1/* 2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import router from '@ohos.router'; 17import { 18 Action, 19 BigDataConstants, 20 BroadCast, 21 BroadCastConstants, 22 BroadCastManager, 23 BrowserConstants, 24 BrowserController, 25 Constants, 26 Log, 27 MediaItem, 28 mMultimodalInputManager, 29 PhotoBrowserBg, 30 PhotoDataSource, 31 PhotoSwiper, 32 ReportToBigDataUtil, 33 ScreenManager, 34 SelectUtil, 35 ThirdSelectManager, 36 ThirdSelectPhotoBrowserActionBar, 37} from '@ohos/common'; 38import { FormConstants, IS_HORIZONTAL } from '../utils/ThirdSelectConstants'; 39import { ThirdSelectedPanel } from './ThirdSelectedPanel'; 40import { MouseTurnPageOperation } from '@ohos/browser'; 41 42const TAG: string = 'thiSel_ThirdSelectPhotoBrowserBase'; 43 44// third selection photoBrowser 45@Component 46export struct ThirdSelectPhotoBrowserBase { 47 @Provide backgroundColorResource: Resource = $r('app.color.default_background_color'); 48 @State totalSelectedCount: number = 0; 49 @Provide broadCast: BroadCast = new BroadCast(); 50 @Provide isSelected: boolean = true; 51 @State isShowBar: boolean = true; 52 @Provide isDefaultBackgroundColor: boolean = true; 53 @State isPhotoScaled: boolean = false; 54 @Provide pageFrom: number = Constants.ENTRY_FROM.NORMAL; 55 selectManager: ThirdSelectManager; 56 bundleName: string = ''; 57 isMultiPick = true; 58 mTransition: string; 59 controller: SwiperController = new SwiperController(); 60 @Provide('transitionIndex') currentIndex: number = 0; 61 @State currentUri: string = ''; 62 isFromFa: boolean = false; 63 @Provide canSwipe: boolean = true; 64 // position 65 mPosition: number; 66 @State title: string = ''; 67 @Prop @Watch('onPageChanged') pageStatus: boolean; 68 @StorageLink(IS_HORIZONTAL) isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 69 maxSelectCount: number; 70 @StorageLink('geometryOpacity') geometryOpacity: number = 1; 71 @State @Watch('onGeometryChanged') geometryTransitionId: string = 'default_id'; 72 @Link isRunningAnimation: boolean; 73 @ObjectLink browserController: BrowserController; 74 @Provide isDeleting: boolean = false; 75 // DataSource 76 private dataSource: ThirdBrowserDataSource = new ThirdBrowserDataSource(); 77 private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 78 private geometryTransitionEnable: boolean = false; 79 private isSelectMode: boolean; 80 81 onGeometryChanged() { 82 AppStorage.SetOrCreate<string>('geometryTransitionBrowserId', this.geometryTransitionId); 83 } 84 85 aboutToAppear(): void { 86 Log.info(TAG, 'photoBrowser aboutToAppear'); 87 this.backgroundColorResource = $r('app.color.black'); 88 this.isDefaultBackgroundColor = false; 89 this.geometryTransitionId = AppStorage.Get('geometryTransitionBrowserId'); 90 this.browserController.browserBackFunc = this.onBackPress.bind(this); 91 mMultimodalInputManager.registerListener((control: number) => { 92 Log.info(TAG, `key control : ${control} index ${this.currentIndex}`); 93 if (control == 0) { 94 if (this.currentIndex > 0) { 95 this.onPhotoChanged(this.currentIndex - 1); 96 } 97 } else if (control == 1) { 98 if (this.currentIndex < this.dataSource.totalCount() - 1) { 99 this.onPhotoChanged(this.currentIndex + 1); 100 } 101 } else { 102 this.onBackPress(); 103 } 104 }); 105 this.selectManager = AppStorage.Get(Constants.THIRD_SELECT_MANAGER); 106 this.dataSource.setAlbumDataSource(AppStorage.Get(Constants.APP_KEY_PHOTO_BROWSER)); 107 this.isMultiPick = this.selectManager.getIsMultiPick(); 108 if (this.isMultiPick == true) { 109 this.totalSelectedCount = this.selectManager.getSelectedCount(); 110 } else { 111 this.totalSelectedCount = Constants.NUMBER_1; 112 } 113 114 let param: any = this.browserController.browserParam; 115 this.isFromFa = param.isFromFa; 116 this.isSelectMode = param.selectMode; 117 if (param.selectMode) { 118 this.dataSource.setSelectMode(this.selectManager); 119 } 120 this.onPhotoChanged(param.position); 121 this.mTransition = param.transition; 122 this.bundleName = param.bundleName; 123 this.title = param.title; 124 this.maxSelectCount = param.maxSelectCount; 125 this.onMenuClicked = this.onMenuClicked.bind(this); 126 127 this.dataSource.setBroadCast(this.broadCast); 128 129 this.broadCast.on(BrowserConstants.PULL_DOWN_END, this.onBackPress.bind(this)); 130 this.broadCast.on(BrowserConstants.DATA_SIZE_CHANGED, this.onDataSizeChanged.bind(this)); 131 this.broadCast.on(BroadCastConstants.SELECT, this.selectCallback.bind(this)); 132 this.broadCast.on(BrowserConstants.DATA_CONTENT_CHANGED, this.onPhotoChanged.bind(this, this.currentIndex)); 133 this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpBrowserCallback.bind(this)); 134 this.broadCast.on(BrowserConstants.SET_DISABLE_SWIPE, (value: boolean) => { 135 Log.info(TAG, `set swiper swipe ${value}`); 136 this.canSwipe = value; 137 }); 138 if (this.pageStatus) { 139 this.onPageShow(); 140 } 141 } 142 143 aboutToDisappear(): void { 144 this.broadCast.release(); 145 this.dataSource.release(); 146 mMultimodalInputManager.unregisterListener(); 147 this.controller = undefined; 148 } 149 150 onDataSizeChanged(size: number): void { 151 Log.info(TAG, `onDataSizeChanged, size is ${size}`); 152 if (size == 0) { 153 this.onBackPress(); 154 } 155 } 156 157 onPhotoChanged(index: number): void { 158 this.currentIndex = index; 159 let currentPhoto = this.getCurrentPhoto(); 160 if (currentPhoto == undefined) { 161 Log.error(TAG, 'onPhotoChanged, item is undefined'); 162 } else { 163 this.isSelected = this.selectManager.isItemSelected(currentPhoto.uri); 164 this.currentUri = currentPhoto.uri; 165 166 let dataSourceIndex = this.isSelectMode ? this.selectManager.getSelectItemDataSourceIndex(currentPhoto) : index; 167 let timelineIndex = this.dataSource.getPositionByIndex(dataSourceIndex); 168 AppStorage.SetOrCreate<number>('placeholderIndex', timelineIndex); 169 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 170 Log.info(TAG, `onPhotoChanged, index: ${index}, currentPhoto: ${currentPhoto.uri}, \ 171 geometryTransitionId = ${this.geometryTransitionId}, placeholderIndex = ${timelineIndex}`); 172 } 173 } 174 175 selectStateChange() { 176 Log.info(TAG, 'change selected.'); 177 let currentPhoto = this.getCurrentPhoto(); 178 if (currentPhoto == undefined) { 179 return; 180 } 181 this.isSelected = !this.isSelected; 182 if (this.isSelected) { 183 this.selectManager.toggle(currentPhoto.uri, true); 184 } else { 185 this.selectManager.toggle(currentPhoto.uri, false); 186 } 187 this.totalSelectedCount = this.selectManager.getSelectedCount(); 188 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 189 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change geometryTransitionId ${this.geometryTransitionId}`); 190 } 191 192 selectCallback(position: number, key: string, value: boolean) { 193 if (key === this.currentUri) { 194 this.isSelected = value; 195 } 196 if (this.selectManager) { 197 this.selectManager.toggle(key, value); 198 } 199 this.totalSelectedCount = this.selectManager.getSelectedCount(); 200 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after select callback`); 201 } 202 203 onPageChanged() { 204 if (this.pageStatus) { 205 this.onPageShow(); 206 } else { 207 this.onPageHide(); 208 } 209 } 210 211 onPageShow() { 212 Log.debug(TAG, 'onPageShow'); 213 this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []); 214 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [true, this.mTransition]); 215 } 216 217 onPageHide() { 218 Log.debug(TAG, 'onPageHide'); 219 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [false, this.mTransition]); 220 } 221 222 onMenuClicked(action: Action) { 223 Log.info(TAG, `onMenuClicked, action: ${action.actionID}`); 224 switch (action.actionID) { 225 case Action.BACK.actionID: 226 let msg = { 227 'From': BigDataConstants.BY_CLICK, 228 } 229 ReportToBigDataUtil.report(BigDataConstants.ESC_PHOTO_BROWSER_WAY, msg); 230 this.onBackPress(); 231 return; 232 case Action.MATERIAL_SELECT.actionID: 233 Log.info(TAG, 'click UN_SELECTED'); 234 this.selectStateChange(); 235 return; 236 case Action.SELECTED.actionID: 237 Log.info(TAG, 'click SELECTED'); 238 this.selectStateChange(); 239 return; 240 case Action.OK.actionID: 241 Log.info(TAG, 'click OK'); 242 this.setPickResult(); 243 break; 244 default: 245 break; 246 } 247 } 248 249 getCurrentPhoto(): MediaItem { 250 Log.debug(TAG, 'getCurrentPhoto ' + this.currentIndex); 251 return this.dataSource.getData(this.currentIndex)?.data; 252 } 253 254 onBackPress() { 255 if (this.geometryTransitionEnable) { 256 this.controller.finishAnimation(this.onBackPressInner.bind(this)); 257 } else { 258 router.back({ 259 url: '', 260 params: { index: this.currentIndex } 261 }); 262 } 263 return true; 264 } 265 266 @Builder buildCheckBox() { 267 if (this.isMultiPick) { 268 Row() { 269 Image(this.isSelected ? $r('app.media.picker_checkbox_selected_dark') : $r('app.media.picker_checkbox_unselected_dark')) 270 .width($r('app.float.icon_size')) 271 .aspectRatio(1) 272 .key('Checkbox_' + this.currentIndex) 273 .margin({ 274 right: $r('sys.float.ohos_id_max_padding_end'), 275 bottom: $r('app.float.picker_browser_checkbox_margin_bottom') 276 }) 277 .onClick(() => { 278 this.selectStateChange(); 279 }) 280 } 281 .justifyContent(FlexAlign.End) 282 .width('100%') 283 .visibility(this.isShowBar ? Visibility.Visible : Visibility.Hidden) 284 .opacity(this.geometryOpacity) 285 // @ts-ignore 286 .transition(TransitionEffect.opacity(0)) 287 .hitTestBehavior(HitTestMode.Transparent) 288 } 289 } 290 291 @Builder buildPanel() { 292 ThirdSelectedPanel({ 293 maxSelectCount: this.maxSelectCount, 294 onMenuClicked: this.onMenuClicked, 295 isBrowserMode: true, 296 isMultiPick: this.isMultiPick, 297 mTransition: TAG, 298 isFromFa: this.isFromFa, 299 currentUri: this.currentUri, 300 isShowBar: $isShowBar, 301 totalSelectedCount: $totalSelectedCount 302 }) 303 .opacity(this.geometryOpacity) 304 // @ts-ignore 305 .transition(TransitionEffect.opacity(0)) 306 .hitTestBehavior(HitTestMode.Transparent) 307 } 308 309 build() { 310 Stack({ alignContent: Alignment.Bottom }) { 311 Stack({ alignContent: Alignment.TopStart }) { 312 PhotoBrowserBg({ isShowBar: $isShowBar }) 313 .opacity(this.geometryOpacity) 314 // @ts-ignore 315 .transition(TransitionEffect.opacity(0)) 316 317 PhotoSwiper({ 318 dataSource: this.dataSource, 319 mTransition: this.mTransition, 320 onPhotoChanged: this.onPhotoChanged.bind(this), 321 swiperController: this.controller, 322 verifyPhotoScaledFunc: this.verifyPhotoScaled.bind(this), 323 geometryTransitionEnable: true, 324 broadCast: $broadCast, 325 isRunningAnimation: $isRunningAnimation 326 }) 327 328 if (this.isHorizontal) { 329 MouseTurnPageOperation({ 330 dataSource: this.dataSource, 331 controller: this.controller, 332 isPhotoScaled: this.isPhotoScaled, 333 isShowBar: this.isShowBar 334 }) 335 .opacity(this.geometryOpacity) 336 // @ts-ignore 337 .transition(TransitionEffect.opacity(0)) 338 .hitTestBehavior(HitTestMode.Transparent) 339 340 } 341 ThirdSelectPhotoBrowserActionBar({ 342 isMultiPick: this.isMultiPick, 343 onMenuClicked: this.onMenuClicked, 344 title: this.title, 345 isThird: true, 346 isShowBar: $isShowBar, 347 totalSelectedCount: $totalSelectedCount 348 }) 349 .opacity(this.geometryOpacity) 350 // @ts-ignore 351 .transition(TransitionEffect.opacity(0)) 352 .hitTestBehavior(HitTestMode.Transparent) 353 } 354 355 this.buildCheckBox() 356 this.buildPanel() 357 } 358 .padding({ bottom: $r('app.float.title_default') }) 359 } 360 361 pageTransition() { 362 PageTransitionEnter({ type: RouteType.None, duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION }) 363 .opacity(0) 364 PageTransitionExit({ duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION }) 365 .opacity(0) 366 } 367 368 verifyPhotoScaled(matrix: any) { 369 if (!!matrix) { 370 let mat = matrix.copy().matrix4x4 371 let xScale = mat[Constants.NUMBER_0] 372 let yScale = mat[Constants.NUMBER_5] 373 Log.info(TAG, `photo in PhotoItem has Scaled x scale: ${xScale}, y scale: ${yScale}, mat: ${mat}`) 374 this.isPhotoScaled = xScale != 1 || yScale != 1 375 } else { 376 this.isPhotoScaled = false 377 Log.info(TAG, `photo in PhotoItem has not Scaled isPhotoScaled: ${this.isPhotoScaled}`) 378 } 379 } 380 381 private onBackPressInner(): void { 382 this.browserController.hideBrowser(); 383 } 384 385 private jumpBrowserCallback(name: string, item: MediaItem, isSelectMode = false) { 386 if (this.dataSource && item && this.currentUri != item.uri) { 387 let tgtIndex = this.dataSource.getDataIndex(item); 388 Log.debug(TAG, `jump to index ${tgtIndex}`) 389 this.onPhotoChanged(tgtIndex); 390 } 391 } 392 393 private setPickResult(): void { 394 if (this.isFromFa) { 395 let currentPhoto = this.getCurrentPhoto(); 396 if (currentPhoto) { 397 Log.debug(TAG, `setPickResult. updateFormData obj: ${currentPhoto.uri} currentIndex: ${this.currentIndex}`); 398 this.appBroadCast.emit(BroadCastConstants.SAVE_FORM_EDITOR_DATA, 399 ["", AppStorage.Get(FormConstants.FORM_ITEM_ALBUM_URI), AppStorage.Get(FormConstants.FORM_ITEM_DISPLAY_NAME), 400 currentPhoto.uri, false]); 401 } else { 402 Log.error(TAG, 'Fa setPickResult is null'); 403 } 404 return; 405 } 406 let uriArray; 407 if (this.isMultiPick) { 408 uriArray = SelectUtil.getUriArray(this.selectManager.clickedSet); 409 Log.info(TAG, `uri size: ${uriArray}`); 410 } else { 411 let currentPhoto = this.getCurrentPhoto(); 412 if (currentPhoto == undefined) { 413 return; 414 } 415 uriArray = [currentPhoto.uri]; 416 } 417 let promise: Promise<void> = SelectUtil.grantPermissionForUris(uriArray, this.bundleName); 418 let abilityResult = { 419 'resultCode': 0, 420 'want': { 421 'parameters': { 422 'select-item-list': uriArray, 423 } 424 } 425 }; 426 promise.then(function () { 427 Log.info(TAG, `grant permission success.`); 428 globalThis.photosAbilityContext.terminateSelfWithResult(abilityResult).then((result) => { 429 Log.info(TAG, `terminateSelf result: ${result}`); 430 }); 431 }).catch(function (err) { 432 Log.error(TAG, `grant permission error: ${JSON.stringify(err)}`); 433 }); 434 } 435} 436 437/** 438 * 用于预览已选中的图片的dataSource 439 * 数据源取自selectManager的当前已选中图片 440 */ 441class ThirdBrowserDataSource extends PhotoDataSource { 442 private isSelectMode = false; 443 private selectedItems: MediaItem[] = new Array<MediaItem>(); 444 445 totalCount() { 446 if (this.isSelectMode) { 447 return this.selectedItems.length; 448 } 449 return super.totalCount(); 450 } 451 452 getData(index: number): any { 453 if (this.isSelectMode) { 454 return this.packData(index, this.selectedItems[index]); 455 } 456 return super.getData(index); 457 } 458 459 setSelectMode(manager: ThirdSelectManager) { 460 this.isSelectMode = true; 461 this.selectedItems = manager.getSelectItems(); 462 } 463} 464