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 AlbumDefine, 20 BigDataConstants, 21 BroadCast, 22 BroadCastConstants, 23 BroadCastManager, 24 BrowserController, 25 CommonObserverCallback, 26 Constants, 27 GridScrollBar, 28 ImageGridItemComponent, 29 Log, 30 MediaDataSource, 31 MediaItem, 32 MediaObserver, 33 NoPhotoIndexComponent, 34 PhotoDataImpl, 35 ReportToBigDataUtil, 36 ScreenManager, 37 SelectUtil, 38 ThirdSelectManager, 39 UiUtil, 40 UserFileManagerAccess, 41 ViewData, 42} from '@ohos/common'; 43import { ThirdSelectedPageActionBar } from './ThirdSelectedPageActionBar'; 44import { ThirdSelectedPanel } from './ThirdSelectedPanel'; 45import { CameraGridItemComponent } from './CameraGridItemComponent'; 46import { 47 FormConstants, 48 IS_HORIZONTAL, 49 IS_SPLIT_MODE, 50 LEFT_BLANK, 51 SelectParams, 52 THIRD_SELECT_IS_ORIGIN 53} from '../utils/ThirdSelectConstants'; 54 55const TAG: string = 'thiSel_ThirdSelectPhotoGridBase'; 56 57// Third Select Album Page 58@Component 59export struct ThirdSelectPhotoGridBase { 60 @State selectedCount: number = 0; 61 @Provide isSelectedMode: boolean = true; 62 @Provide moreMenuList: Array<Action> = new Array<Action>(); 63 @Provide rightClickMenuList: Array<Action> = new Array<Action>(); 64 PhotoDataImpl: PhotoDataImpl; 65 dataSource: MediaDataSource = new MediaDataSource(Constants.DEFAULT_SLIDING_WIN_SIZE); 66 @Provide broadCast: BroadCast = new BroadCast(); 67 @Provide isShow: boolean = true; 68 selectManager: ThirdSelectManager; 69 isActive = false; 70 @State title: string = ''; 71 @State isEmpty: boolean = false; 72 @StorageLink(IS_SPLIT_MODE) isSplitMode: boolean = ScreenManager.getInstance().isSplitMode(); 73 @StorageLink(LEFT_BLANK) leftBlank: [number, number, number, number] 74 = [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()]; 75 @StorageLink(IS_HORIZONTAL) isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 76 DEFAULT_TOAST_DURATION = 2000; 77 @State gridRowCount: number = 0; 78 @State isShowScrollBar: boolean = false; 79 @State currentUri: string = ''; 80 @Provide isShowBar: boolean = true; 81 scroller: Scroller = new Scroller(); 82 isFirstEnter: boolean = false; 83 @Prop @Watch('onPageChanged') pageStatus: boolean; 84 backFuncBinder: Function; 85 @State selectParams: SelectParams = SelectParams.defaultParam(); 86 @State screenHeight: number = ScreenManager.getInstance().getWinHeight(); 87 @StorageLink('placeholderIndex') @Watch('onPlaceholderChanged') placeholderIndex: number = -1; 88 @ObjectLink @Watch('onBrowserControllerChanged') browserController: BrowserController; 89 private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 90 private dataObserver: CommonObserverCallback = new CommonObserverCallback(this); 91 private selectFromCameraFunc: () => void; 92 private itemId: string = undefined; 93 private isItemIdChange: boolean = false; 94 onWindowSizeChangeCallBack = () => this.initGridRowCount(); 95 96 onPlaceholderChanged() { 97 Log.debug(TAG, 'onPlaceholderChanged placeholderIndex is ' + this.placeholderIndex); 98 if (this.placeholderIndex != -1) { 99 this.scroller.scrollToIndex(this.placeholderIndex); 100 } 101 } 102 103 onBrowserControllerChanged(): void { 104 if (!this.browserController.isBrowserShow) { 105 ScreenManager.getInstance().setSystemUi(true); 106 } 107 } 108 109 onMenuClicked(action: Action) { 110 Log.info(TAG, `onMenuClicked, action: ${action.actionID}`); 111 switch (action.actionID) { 112 case Action.BACK.actionID: 113 this.goBackFormEditor(); 114 break; 115 case Action.CANCEL.actionID: 116 this.setPickResult(null); 117 break; 118 case Action.OK.actionID: 119 this.setPickResult(SelectUtil.getUriArray(this.selectManager.clickedSet)); 120 break; 121 case Action.NAVIGATION_ALBUMS.actionID: 122 let params: any = this.selectParams; 123 params.isFirstEnter = false; 124 let options = { 125 url: 'pages/ThirdSelectAlbumSetPage', 126 params: params 127 } 128 router.pushUrl(options); 129 ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_SWITCH_ALBUM, null); 130 break; 131 default: 132 break; 133 } 134 } 135 136 aboutToAppear(): void { 137 let param: any = router.getParams(); 138 this.initSelectParams(param); 139 if (this.selectParams.isFromFa) { 140 this.selectParams.filterMediaType = AlbumDefine.FILTER_MEDIA_TYPE_IMAGE; 141 AppStorage.SetOrCreate(FormConstants.FORM_ITEM_ALBUM_URI, param.uri); 142 AppStorage.SetOrCreate(FormConstants.FORM_ITEM_DISPLAY_NAME, param.itemDisplayName); 143 } 144 this.dataSource.setFilterMediaType(this.selectParams.filterMediaType); 145 this.initSelectManager(); 146 this.selectManager.setIsMultiPick(this.selectParams.isMultiPick); 147 148 let self = this; 149 // 后续phone缩略图支持横竖屏后再放开 150 if (AppStorage.Get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 151 ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 152 } 153 this.initGridRowCount(); 154 this.onMenuClicked = this.onMenuClicked.bind(this); 155 this.dataSource.setBroadCast(this.broadCast); 156 this.broadCast.on(BroadCastConstants.SELECT, this.onSelectCallback.bind(this)); 157 this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpBrowserCallback.bind(this)); 158 this.broadCast.on(Constants.ON_LOADING_FINISHED, 159 (size: number) => { 160 this.onLoadFinishedCallback(size, this.updateTitle.bind(this, param)); 161 }); 162 this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onReloadFinishedCallback.bind(this)); 163 this.selectManager.registerCallback('updateCount', 164 (newState: number) => { 165 Log.info(TAG, `updateSelectedCount ${newState}`); 166 self.selectedCount = newState; 167 self.selectManager.emitCallback('thirdSelectUpdateCount', [newState]); 168 }); 169 this.dataSource.registerCallback('updateCount', 170 (newState: number) => { 171 Log.info(TAG, `updateTotalCount ${newState}`); 172 self.isShowScrollBar = (newState > Constants.PHOTOS_CNT_FOR_HIDE_SCROLL_BAR); 173 self.selectManager.setTotalCount(newState); 174 }); 175 MediaObserver.getInstance().registerObserver(this.dataObserver); 176 this.isActive = true; 177 178 Log.error(TAG, 'meow data count = ' + this.dataSource.totalCount()); 179 } 180 181 onBackPress() { 182 this.onMenuClicked(this.selectParams.isFromFa ? Action.BACK : Action.CANCEL); 183 } 184 185 onPageShow() { 186 Log.debug(TAG, 'onPageShow'); 187 let param: any = router.getParams(); 188 this.isItemIdChange = this.itemId && param && this.itemId !== param.itemId; 189 if (this.isItemIdChange) { 190 this.initSelectParams(param); 191 } 192 193 MediaObserver.getInstance().registerObserver(this.dataObserver); 194 this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []); 195 this.isShow = true; 196 if (!this.browserController.isBrowserShow) { 197 ScreenManager.getInstance().setSystemUi(true); 198 } 199 this.onActive(); 200 } 201 202 onPageChanged() { 203 if (this.pageStatus) { 204 this.onPageShow(); 205 } else { 206 this.onPageHide(); 207 } 208 } 209 210 onPageHide() { 211 Log.debug(TAG, 'onPageHide'); 212 this.isShow = false; 213 this.onInActive(); 214 } 215 216 aboutToDisappear(): void { 217 ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 218 this.onWindowSizeChangeCallBack = null; 219 MediaObserver.getInstance().unregisterObserver(this.dataObserver); 220 this.dataObserver.clearSource(); 221 this.broadCast.off(null, null); 222 this.dataSource.releaseBroadCast(); 223 Log.info(TAG, `call aboutToDisappear`) 224 } 225 226 onMediaLibDataChange(changeType) { 227 Log.info(TAG, `onMediaLibDataChange type: ${changeType}`); 228 this.dataSource.onChange(changeType); 229 } 230 231 getGeometryTransitionId(item: ViewData, index: number): string { 232 return TAG + item.mediaItem.getHashCode() + this.selectManager.isItemSelected(item?.mediaItem?.uri); 233 } 234 235 @Builder buildGrid() { 236 Grid(this.scroller) { 237 if (!this.selectParams.isFromFa) { 238 GridItem() { 239 CameraGridItemComponent({ 240 selectParams: this.selectParams, 241 updateDataFunc: (uri: string) => { 242 Log.debug(TAG, `get camera callback, uri ${uri}`) 243 this.dataSource.initData(); 244 this.selectFromCameraFunc = () => { 245 this.onSelectCallback(0, uri, true, null); 246 } 247 } 248 }) 249 } 250 .aspectRatio(1) 251 } 252 LazyForEach(this.dataSource, (item, index?: number) => { 253 if (!!item) { 254 GridItem() { 255 ImageGridItemComponent({ 256 dataSource: this.dataSource, 257 item: item?.mediaItem, 258 isSelected: this.selectManager.isItemSelected(item?.mediaItem?.uri), 259 pageName: Constants.PHOTO_TRANSITION_ALBUM, 260 isThird: true, 261 mPosition: item?.viewIndex, 262 isThirdMultiPick: this.selectParams.isMultiPick, 263 geometryTransitionString: this.getGeometryTransitionId(item, index), 264 selectedCount: $selectedCount 265 }) 266 } 267 .aspectRatio(1) 268 .zIndex(index === this.placeholderIndex ? 1 : 0) 269 } 270 }, (item, index) => { 271 if (item == null || item == undefined) { 272 return JSON.stringify(item) + index; 273 } 274 return this.getGeometryTransitionId(item, index); 275 }) 276 } 277 .edgeEffect(EdgeEffect.Spring) 278 .scrollBar(BarState.Off) 279 .gridStyle(this.gridRowCount) 280 } 281 282 build() { 283 Column() { 284 ThirdSelectedPageActionBar({ 285 leftAction: this.selectParams.isFromFa ? Action.BACK : Action.CANCEL, 286 isSelectPhotoGrid: true, 287 title: $title, 288 selectParams: this.selectParams, 289 onMenuClicked: this.onMenuClicked, 290 isFirstEnter: this.isFirstEnter, 291 totalSelectedCount: $selectedCount 292 }) 293 294 if (this.selectParams.isFromFa && this.isEmpty) { 295 NoPhotoIndexComponent({ index: Constants.TIMELINE_PAGE_INDEX, hasBarSpace: false }) 296 } 297 Stack() { 298 this.buildGrid() 299 if (this.isShowScrollBar) { 300 GridScrollBar({ scroller: this.scroller }); 301 } 302 } 303 .layoutWeight(1) 304 305 if (this.selectParams.isMultiPick) { 306 ThirdSelectedPanel({ 307 maxSelectCount: this.selectParams.maxSelectCount, 308 onMenuClicked: this.onMenuClicked, 309 mTransition: TAG, 310 currentUri: this.currentUri, 311 isShowBar: $isShowBar, 312 totalSelectedCount: $selectedCount, 313 dataSource: this.dataSource 314 }) 315 } 316 } 317 .backgroundColor($r('sys.color.ohos_id_color_sub_background')) 318 .padding({ 319 top: this.leftBlank[1], 320 bottom: this.leftBlank[3] 321 }) 322 } 323 324 jumpToBrowserNormal(targetIndex: number, name: string, item: MediaItem, isSelectMode = false): void { 325 router.pushUrl({ 326 url: 'pages/ThirdSelectPhotoBrowser', 327 params: { 328 position: targetIndex, 329 bundleName: this.selectParams.bundleName, 330 transition: name, 331 title: this.title, 332 selectMode: isSelectMode, 333 maxSelectCount: this.selectParams.maxSelectCount, 334 isFromFa: this.selectParams.isFromFa 335 } 336 }); 337 } 338 339 jumpToBrowserGeometryTransition(targetIndex: number, name: string, item: MediaItem, isSelectMode = false, 340 geometryTapIndex: number = undefined, 341 geometryTransitionString: string = undefined): void { 342 this.browserController.showBrowser(geometryTapIndex, geometryTransitionString, TAG, { 343 position: targetIndex, 344 bundleName: this.selectParams.bundleName, 345 transition: name, 346 title: this.title, 347 selectMode: isSelectMode, 348 maxSelectCount: this.selectParams.maxSelectCount, 349 isFromFa: this.selectParams.isFromFa 350 }); 351 } 352 353 private initGridRowCount(): void { 354 let contentWidth = ScreenManager.getInstance().getWinWidth(); 355 let margin = 0; 356 let maxThumbWidth = px2vp(Constants.GRID_IMAGE_SIZE) * Constants.GRID_MAX_SIZE_RATIO; 357 let calCount = Math.round( 358 ((contentWidth - Constants.NUMBER_2 * margin) + Constants.GRID_GUTTER) 359 / (maxThumbWidth + Constants.GRID_GUTTER)); 360 let newCount = Math.max(Constants.GRID_MIN_COUNT, calCount); 361 if (newCount != this.gridRowCount) { 362 this.gridRowCount = newCount; 363 } 364 Log.info(TAG, `initGridRowCount contentWidth: ${contentWidth}, row count ${this.gridRowCount}`); 365 } 366 367 private initSelectParams(param) { 368 if (param != null) { 369 this.isItemIdChange = this.itemId && this.itemId !== param.itemId; 370 this.itemId = param.itemId == undefined ? AlbumDefine.ALBUM_ID_ALL : param.itemId; 371 this.dataSource.setAlbumUri(this.itemId); 372 373 let albumUri = param.uri == undefined ? 374 UserFileManagerAccess.getInstance().getSystemAlbumUri(UserFileManagerAccess.IMAGE_ALBUM_SUB_TYPE) : 375 param.uri; 376 this.dataSource.setAlbumUri(albumUri); 377 this.updateTitle(param); 378 this.selectParams.bundleName = param.bundleName; 379 this.selectParams.isMultiPick = param.isMultiPick; 380 if (param.isFromFa != undefined || param.isFromFa != null) { 381 this.selectParams.isFromFa = param.isFromFa; 382 } 383 if (param.isFromFaPhoto != undefined || param.isFromFaPhoto != null) { 384 this.selectParams.isFromFaPhoto = param.isFromFaPhoto; 385 } 386 if (param.isFirstEnter != undefined || param.isFirstEnter != null) { 387 this.isFirstEnter = param.isFirstEnter; 388 } 389 if (!!param.filterMediaType) { 390 this.selectParams.filterMediaType = param.filterMediaType; 391 } 392 this.selectParams.isFromWallpaper = param.isFromWallpaper; 393 if (this.selectParams.isFromWallpaper) { 394 this.selectParams.maxSelectCount = param.remainingOfWallpapers; 395 } else if (!!param.maxSelectCount && param.maxSelectCount > 0) { 396 this.selectParams.maxSelectCount = param.maxSelectCount > Constants.LIMIT_MAX_THIRD_SELECT_COUNT 397 ? Constants.LIMIT_MAX_THIRD_SELECT_COUNT 398 : param.maxSelectCount; 399 } 400 if (this.backFuncBinder) { 401 this.backFuncBinder(this.onBackPress.bind(this)); 402 } 403 Log.debug(TAG, `select param ${JSON.stringify(this.selectParams)}`); 404 } 405 this.isSelectedMode = this.selectParams.isMultiPick; 406 Log.debug(TAG, `select param ${JSON.stringify(this.selectParams)}, select mode ${this.isSelectedMode}`); 407 } 408 409 private updateTitle(param) { 410 let displayName = param.itemDisplayName == undefined ? $r('app.string.album_all') : param.itemDisplayName; 411 if (typeof displayName === 'object') { 412 UiUtil.getResourceString(displayName).then((stringResource) => { 413 this.title = stringResource; 414 }) 415 } else { 416 this.title = displayName; 417 } 418 Log.debug(TAG, `update title ${this.title}`); 419 } 420 421 private onSelectCallback(position: number, key: string, value: boolean, callback: Function) { 422 Log.debug(TAG, `isHorizontal ${this.isHorizontal}, position ${position}, uri ${key}, select ${value}`) 423 let isMultiPick = this.selectParams.isMultiPick; 424 if (value && isMultiPick && this.selectedCount >= this.selectParams.maxSelectCount) { 425 if (!this.isHorizontal) { 426 UiUtil.showToast($r('app.string.up_to_limit_tips')); 427 } 428 return; 429 } 430 if (!isMultiPick) { 431 // update correct status from select manager 432 value = !this.selectManager.isItemSelected(key); 433 this.selectManager.deSelectAll(); 434 } 435 if (this.selectManager.toggle(key, value, position)) { 436 Log.info(TAG, 'enter event process'); 437 this.dataSource.onDataChanged(this.dataSource.getDataIndexByUri(key)); 438 callback && callback(value); 439 } 440 } 441 442 private jumpBrowserCallback(name: string, item: MediaItem, geometryTapIndex: number = undefined, 443 geometryTransitionString: string = undefined, isSelectMode = false) { 444 let targetIndex = isSelectMode ? this.selectManager.getSelectItemIndex(item) : this.dataSource.getDataIndex(item); 445 Log.info(TAG, `jump to photo browser at index: ${targetIndex}, transition: ${name}`); 446 AppStorage.SetOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource); 447 if (geometryTapIndex != undefined && geometryTransitionString != undefined) { 448 this.jumpToBrowserGeometryTransition( 449 targetIndex, name, item, isSelectMode, geometryTapIndex, geometryTransitionString); 450 } else { 451 this.jumpToBrowserNormal(targetIndex, name, item, isSelectMode); 452 } 453 ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_CLICK_PREVIEW, null); 454 } 455 456 private onReloadFinishedCallback() { 457 Log.info(TAG, 'ON_DATA_RELOADED'); 458 this.dataSource.onDataReloaded(); 459 this.selectFromCameraFunc && this.selectFromCameraFunc(); 460 this.selectFromCameraFunc = undefined; 461 } 462 463 private onLoadFinishedCallback(size: number, updateTitle: Function) { 464 Log.info(TAG, `ON_LOADING_FINISHED size: ${size}`); 465 this.isEmpty = size == 0; 466 if (this.isEmpty && updateTitle) { 467 updateTitle(); 468 } 469 Log.info(TAG, `isEmpty: ${this.isEmpty}`) 470 this.dataSource.onDataReloaded(); 471 } 472 473 private initSelectManager() { 474 let manager: ThirdSelectManager = AppStorage.Get(Constants.THIRD_SELECT_MANAGER); 475 if (manager && manager.getClassName() === 'ThirdSelectManager') { 476 Log.debug(TAG, `use cached select manager, current select count ${manager.getSelectedCount()}`); 477 this.selectManager = manager; 478 } else { 479 Log.debug(TAG, 'create new select manager'); 480 this.selectManager = new ThirdSelectManager(); 481 AppStorage.SetOrCreate(Constants.THIRD_SELECT_MANAGER, this.selectManager); 482 } 483 if (this.isFirstEnter) { 484 Log.debug(TAG, 'clear select manager'); 485 this.selectManager.deSelectAll(); 486 AppStorage.SetOrCreate(THIRD_SELECT_IS_ORIGIN, false); 487 } 488 this.selectManager.setGetMediaItemFunc(this.dataSource.getMediaItemByUri.bind(this.dataSource)); 489 } 490 491 private onActive() { 492 if (this.isItemIdChange) { 493 this.isActive = false; 494 this.dataSource && this.dataSource.initData(); 495 } 496 497 if (!this.isActive) { 498 Log.info(TAG, 'onActive'); 499 this.isActive = true; 500 this.dataSource && this.dataSource.onActive(); 501 if (this.selectParams.isMultiPick) { 502 this.dataSource.forceUpdate(); 503 } 504 } 505 } 506 507 private onInActive() { 508 if (this.isActive) { 509 Log.info(TAG, 'onInActive'); 510 this.isActive = false; 511 this.dataSource && this.dataSource.onInActive(); 512 } 513 } 514 515 private setPickResult(uriArray: Array<string>): void { 516 let isOrigin: boolean = AppStorage.Get(THIRD_SELECT_IS_ORIGIN); 517 if (isOrigin == undefined) { 518 isOrigin = false; 519 } 520 let abilityResult = { 521 'resultCode': 0, 522 'want': { 523 'parameters': { 524 'select-item-list': uriArray, 525 'isOriginal': isOrigin 526 } 527 } 528 }; 529 let self = this; 530 let uriLength = 0; 531 if (uriArray == null && uriArray == undefined) { 532 globalThis.photosAbilityContext.terminateSelfWithResult(abilityResult).then((result) => { 533 Log.debug(TAG, `terminateSelfWithResult result: ${result}, self result ${JSON.stringify(abilityResult)}`); 534 }); 535 } else { 536 uriLength = uriArray.length; 537 SelectUtil.grantPermissionForUris(uriArray, self.selectParams.bundleName).then(function () { 538 Log.info(TAG, `grant permission success.`); 539 globalThis.photosAbilityContext.terminateSelfWithResult(abilityResult).then((result) => { 540 Log.debug(TAG, `terminateSelfWithResult result: ${result}, self result ${JSON.stringify(abilityResult)}`); 541 }); 542 }).catch(function (err) { 543 Log.error(TAG, `grant permission error: ${JSON.stringify(err)}, self result ${JSON.stringify(abilityResult)}`); 544 }); 545 } 546 ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_RESULT, 547 { "isOriginalChecked": isOrigin, "selectItemSize": uriLength }); 548 } 549 550 private goBackFormEditor() { 551 let formEditorOption = { 552 url: 'pages/FormEditorPage' 553 }; 554 router.replaceUrl(formEditorOption); 555 } 556} 557 558@Extend(Grid) function gridStyle(gridCount: number) { 559 .columnsTemplate('1fr '.repeat(gridCount)) 560 .columnsGap(Constants.GRID_GUTTER) 561 .rowsGap(Constants.GRID_GUTTER) 562 .cachedCount(Constants.GRID_CACHE_ROW_COUNT) 563 .layoutWeight(1) 564} 565