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 AlbumInfo, 20 BroadCast, 21 BroadCastConstants, 22 BroadCastManager, 23 Constants, 24 JumpSourceToMain, 25 Log, 26 MediaDataSource, 27 MediaItem, 28 ScreenManager, 29 SelectManager, 30 TraceControllerUtils, 31 UiUtil, 32 ViewData, 33} from '@ohos/common'; 34import { 35 BrowserController, 36 CustomDialogView, 37 GridScrollBar, 38 ImageGridItemComponent, 39 MoveOrCopyBroadCastProp, 40 NoPhotoComponent 41} from '@ohos/common/CommonComponents'; 42import { AlbumSelectActionBar } from '@ohos/browser/BrowserComponents'; 43import { PhotoBrowserComponent } from '../view/PhotoBrowserComponent'; 44import { SelectPhotoBrowserView } from '../view/SelectPhotoBrowserView'; 45 46const TAG: string = 'NewAlbumPage'; 47AppStorage.setOrCreate('photoGridPageIndex', Constants.INVALID); 48 49interface Params { 50 item: string; 51} 52 53@Entry 54@Component 55export struct NewAlbumPage { 56 @State isEmpty: boolean = false; 57 @State isShowScrollBar: boolean = false; 58 @State gridRowCount: number = 0; 59 @Provide isSelectedMode: boolean = true; 60 @Provide isAllSelected: boolean = false; 61 @State totalSelectedCount: number = 0; 62 @StorageLink('isHorizontal') isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 63 @Provide broadCast: BroadCast = new BroadCast(); 64 @Provide isShow: boolean = true; 65 @Provide isShowBar: boolean = true; 66 @State moreMenuList: Action[] = []; 67 @Provide rightClickMenuList: Action[] = []; 68 @State isClickScrollBar: boolean = false; 69 @StorageLink('photoGridPageIndex') @Watch('onIndexChange') photoGridPageIndex: number = Constants.INVALID; 70 @StorageLink('isSplitMode') isSplitMode: boolean = ScreenManager.getInstance().isSplitMode(); 71 @StorageLink('leftBlank') leftBlank: number[] = 72 [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()]; 73 title: string = ''; 74 @StorageLink('placeholderIndex') @Watch('onPlaceholderChanged') placeholderIndex: number = -1; 75 @State pageStatus: boolean = false; 76 @State isRunningAnimation: boolean = false; 77 @State @Watch('updateAnimationStatus') browserController: BrowserController = new BrowserController(true); 78 private dataSource: MediaDataSource = new MediaDataSource(Constants.DEFAULT_SLIDING_WIN_SIZE); 79 private scroller: Scroller = new Scroller(); 80 private isDataFreeze = false; 81 private mSelectManager: SelectManager | null = null; 82 private isActive = false; 83 private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 84 private isNewAlbum: boolean = AppStorage.get<boolean>(Constants.APP_KEY_NEW_ALBUM) as boolean; 85 private onWindowSizeChangeCallBack: Function = () => { 86 // 后续phone缩略图支持横竖屏后再放开 87 } 88 private onUpdateFavorStateFunc: Function = (item: MediaItem): void => this.onUpdateFavorState(item); 89 private selectFunc: Function = (position: number, key: string, value: boolean, callback: Function): void => 90 this.select(position, key, value, callback); 91 private jumpPhotoBrowserFunc: Function = (name: string, item: MediaItem): void => this.jumpPhotoBrowser(name, item); 92 private jumpThirdPhotoBrowserFunc: Function = (name: string, item: MediaItem, 93 geometryTapIndex: number, geometryTransitionString: string): void => 94 this.jumpThirdPhotoBrowser(name, item, geometryTapIndex, geometryTransitionString); 95 private onDataReloadedFunc: Function = (size: number): void => this.onDataReloaded(size); 96 private onLoadingFinishedFunc: Function = (): void => this.onLoadingFinished(); 97 98 private select(position: number, key: string, value: boolean, callback: Function): void { 99 if (this.mSelectManager?.toggle(key, value, position)) { 100 Log.info(TAG, 'enter event process'); 101 callback(); 102 } 103 } 104 105 private jumpPhotoBrowser(name: string, item: MediaItem): void { 106 let targetIndex = this.dataSource.getDataIndex(item); 107 if (targetIndex == Constants.NOT_FOUND) { 108 Log.error(TAG, 'targetIndex is not found'); 109 return; 110 } 111 Log.info(TAG, `jump to photo browser at index: ${targetIndex}`); 112 AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource); 113 interface Params { 114 position: number; 115 transition: string; 116 leftBlank: number[]; 117 } 118 let params: Params = { 119 position: targetIndex, 120 transition: name, 121 leftBlank: this.leftBlank, 122 } 123 this.browserController.showBrowserWithNoAnimation(params); 124 } 125 126 private jumpThirdPhotoBrowser(name: string, item: MediaItem, 127 geometryTapIndex: number, geometryTransitionString: string): void { 128 let targetIndex = this.dataSource.getDataIndex(item); 129 Log.info(TAG, `jump to photo browser, index: ${targetIndex}, transition: ${name}`); 130 AppStorage.setOrCreate(Constants.PHOTO_GRID_SELECT_MANAGER, this.mSelectManager); 131 AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource); 132 interface Params { 133 position: number; 134 transition: string; 135 leftBlank: number[]; 136 } 137 138 const params: Params = { 139 position: targetIndex, 140 transition: name, 141 leftBlank: this.leftBlank, 142 }; 143 if (geometryTapIndex && geometryTransitionString) { 144 this.browserController.showSelectBrowser(geometryTapIndex, geometryTransitionString, TAG, params); 145 } else { 146 this.browserController.showSelectBrowserWithNoAnimation(params); 147 } 148 } 149 150 private onDataReloaded(size: number): void { 151 Log.info(TAG, `ON_LOADING_FINISHED size: ${size}`); 152 this.isEmpty = size == 0; 153 Log.info(TAG, `isEmpty: ${this.isEmpty}`); 154 } 155 156 private onLoadingFinished(): void { 157 Log.info(TAG, 'ON_DATA_RELOADED'); 158 this.dataSource.onDataReloaded(); 159 } 160 161 onIndexChange() { 162 Log.info(TAG, `onIndexChange ${this.photoGridPageIndex}`) 163 if (this.photoGridPageIndex != Constants.INVALID) { 164 this.scroller.scrollToIndex(this.photoGridPageIndex); 165 } 166 } 167 168 onPlaceholderChanged() { 169 Log.debug(TAG, 'onPlaceholderChanged placeholderIndex is ' + this.placeholderIndex); 170 if (this.placeholderIndex != -1) { 171 this.scroller.scrollToIndex(this.placeholderIndex); 172 } 173 } 174 175 onMenuClicked(action: Action) { 176 Log.info(TAG, `onMenuClicked, action: ${action.actionID}`); 177 if (action.actionID === Action.CANCEL.actionID) { 178 router.back(); 179 } else if (action.actionID === Action.OK.actionID) { 180 if (this.mSelectManager?.getSelectedCount() == 0) { 181 Log.info(TAG, `onMenuClicked, action: ${action.actionID}, count = 0`); 182 } 183 Log.info(TAG, `onMenuClicked, action: ${action.actionID} newAlbum: ${this.isNewAlbum}`); 184 if (this.isNewAlbum) { 185 AppStorage.setOrCreate(Constants.IS_SHOW_MOVE_COPY_DIALOG, true); 186 let url = 'pages/index'; 187 router.back({ 188 url: url, 189 params: { 190 jumpSource: JumpSourceToMain.ALBUM, 191 } 192 }) 193 } else { 194 MoveOrCopyBroadCastProp.getInstance().doAddOperation(this.broadCast); 195 } 196 } 197 } 198 199 onModeChange() { 200 Log.info(TAG, 'onModeChange'); 201 } 202 203 onPageShow() { 204 this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []); 205 this.isShow = true; 206 this.pageStatus = this.isShow; 207 this.onActive(); 208 } 209 210 onPageHide() { 211 this.isShow = false; 212 this.pageStatus = this.isShow; 213 this.onInActive(); 214 } 215 216 onActive() { 217 if (!this.isActive) { 218 Log.info(TAG, 'onActive'); 219 this.isActive = true; 220 221 this.dataSource && this.dataSource.onActive(); 222 if (this.isSelectedMode && this.mSelectManager) { 223 this.totalSelectedCount = this.mSelectManager.getSelectedCount(); 224 this.dataSource.forceUpdate(); 225 } 226 } 227 } 228 229 onInActive() { 230 if (this.isActive) { 231 Log.info(TAG, 'onInActive'); 232 this.isActive = false; 233 this.dataSource && this.dataSource.onInActive(); 234 } 235 } 236 237 onUpdateFavorState(item: MediaItem) { 238 Log.debug(TAG, 'onUpdateFavorState'); 239 let index = this.dataSource.getIndexByMediaItem(item); 240 if (index != -1) { 241 this.dataSource.onDataChanged(index); 242 } 243 } 244 245 onBackPress() { 246 if (this.browserController.isBrowserShow) { 247 this.doPhotoBrowserViewBack(); 248 return true; 249 } 250 if (this.browserController.isSelectBrowserShow) { 251 this.doSelectPhotoBrowserViewBack(); 252 return true; 253 } 254 return false; 255 } 256 257 doSelectPhotoBrowserViewBack() { 258 this.appBroadCast.emit(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, []); 259 } 260 261 doPhotoBrowserViewBack() { 262 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_BACK_PRESS_EVENT, []); 263 } 264 265 aboutToAppear(): void { 266 TraceControllerUtils.startTrace('PhotoGridPageAboutToAppear'); 267 this.mSelectManager = AppStorage.Get<SelectManager>(Constants.APP_KEY_NEW_ALBUM_SELECTED) as SelectManager; 268 if (this.mSelectManager == null) { 269 this.mSelectManager = new SelectManager(); 270 AppStorage.setOrCreate(Constants.APP_KEY_NEW_ALBUM_SELECTED, this.mSelectManager); 271 } 272 let param: Params = router.getParams() as Params; 273 if (param != null) { 274 Log.debug(TAG, `After router.getParams, param is: ${JSON.stringify(param)}`); 275 let item: AlbumInfo = JSON.parse(param.item) as AlbumInfo; 276 this.title = item.albumName; 277 this.dataSource.setAlbumUri(item.uri); 278 AppStorage.setOrCreate(Constants.APP_KEY_NEW_ALBUM_SOURCE, item.uri); 279 } else { 280 this.title = ''; 281 this.dataSource.setAlbumUri(''); 282 } 283 284 let self = this; 285 this.dataSource.setBroadCast(this.broadCast) 286 this.mSelectManager.setPhotoDataImpl(); 287 this.initGridRowCount(); 288 ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 289 this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc); 290 this.broadCast.on(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc); 291 this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 292 this.broadCast.on(Constants.ON_LOADING_FINISHED, this.onDataReloadedFunc); 293 this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onLoadingFinishedFunc); 294 295 this.appBroadCast.on(BroadCastConstants.UPDATE_DATA_SOURCE, this.onUpdateFavorStateFunc); 296 AppStorage.setOrCreate(Constants.PHOTO_GRID_SELECT_MANAGER, this.mSelectManager); 297 this.mSelectManager.registerCallback('allSelect', (newState: boolean) => { 298 Log.info(TAG, `allSelect ${newState}`); 299 this.isDataFreeze = AppStorage.get<boolean>(Constants.IS_DATA_FREEZE) as boolean; 300 if (this.isDataFreeze) { 301 return; 302 } 303 this.isAllSelected = newState; 304 this.dataSource.forceUpdate(); 305 }); 306 this.mSelectManager.registerCallback('select', (newState: number) => { 307 Log.info(TAG, `select ${newState}`); 308 this.isDataFreeze = AppStorage.get<boolean>(Constants.IS_DATA_FREEZE) as boolean; 309 if (this.isDataFreeze) { 310 return; 311 } 312 this.dataSource.onDataChanged(newState); 313 }); 314 this.mSelectManager.registerCallback('updateCount', (newState: number) => { 315 Log.info(TAG, `updateSelectedCount ${newState}`); 316 this.isDataFreeze = AppStorage.get<boolean>(Constants.IS_DATA_FREEZE) as boolean; 317 if (this.isDataFreeze) { 318 return; 319 } 320 this.moreMenuList = []; 321 this.moreMenuList.push(Boolean(newState) ? Action.INFO : Action.INFO_INVALID); 322 this.totalSelectedCount = newState; 323 }); 324 this.dataSource.registerCallback('updateCount', (newState: number) => { 325 Log.info(TAG, `updateTotalCount ${newState}`); 326 self.isShowScrollBar = (newState > Constants.PHOTOS_CNT_FOR_HIDE_SCROLL_BAR); 327 self.mSelectManager?.setTotalCount(newState); 328 }) 329 330 this.moreMenuList = []; 331 this.moreMenuList.push(Action.INFO); 332 TraceControllerUtils.finishTrace('PhotoGridPageAboutToAppear'); 333 } 334 335 aboutToDisappear(): void { 336 if (this.broadCast) { 337 this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc); 338 this.broadCast.off(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc); 339 this.broadCast.off(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 340 this.broadCast.off(Constants.ON_LOADING_FINISHED, this.onDataReloadedFunc); 341 this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED, this.onLoadingFinishedFunc); 342 } 343 this.appBroadCast.off(BroadCastConstants.UPDATE_DATA_SOURCE, this.onUpdateFavorStateFunc); 344 this.dataSource.releaseBroadCast(); 345 ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 346 AppStorage.delete(Constants.PHOTO_GRID_SELECT_MANAGER); 347 } 348 349 build() { 350 Stack() { 351 Column() { 352 AlbumSelectActionBar({ 353 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 354 totalSelectedCount: $totalSelectedCount, 355 menuList: $moreMenuList 356 }) 357 if (this.isEmpty) { 358 NoPhotoComponent({ 359 title: $r('app.string.no_distributed_photo_head_title_album') 360 }) 361 } else { 362 Stack() { 363 Grid(this.scroller) { 364 LazyForEach(this.dataSource, (item: ViewData, index?: number) => { 365 if (!!item) { 366 GridItem() { 367 ImageGridItemComponent({ 368 dataSource: this.dataSource, 369 item: item.mediaItem, 370 isSelected: this.isSelectedMode ? 371 this.mSelectManager?.isItemSelected((item.mediaItem as MediaItem).uri as string, item.viewIndex) : 372 false, 373 pageName: Constants.PHOTO_TRANSITION_ALBUM, 374 mPosition: index, 375 geometryTransitionString: this.getGeometryTransitionId(item, index as number), 376 selectedCount: $totalSelectedCount 377 }) 378 } 379 .aspectRatio(1) 380 .columnStart(item.viewIndex % this.gridRowCount) 381 .columnEnd(item.viewIndex % this.gridRowCount) 382 .key('NewAlbumPageImage' + index) 383 .zIndex(index === this.placeholderIndex ? 1 : 0) 384 } 385 }, (item: ViewData, index?: number) => { 386 if (item == null || item == undefined) { 387 return JSON.stringify(item) + index; 388 } 389 return this.getGeometryTransitionId(item, index as number); 390 }) 391 } 392 .edgeEffect(EdgeEffect.Spring) 393 .columnsTemplate('1fr '.repeat(this.gridRowCount)) 394 .columnsGap(Constants.GRID_GUTTER) 395 .rowsGap(Constants.GRID_GUTTER) 396 .cachedCount(Constants.GRID_CACHE_ROW_COUNT) 397 398 if (this.isShowScrollBar) { 399 GridScrollBar({ scroller: this.scroller }); 400 } 401 } 402 .layoutWeight(1) 403 } 404 CustomDialogView({ broadCast: $broadCast }) 405 } 406 .backgroundColor($r('app.color.default_background_color')) 407 .margin({ 408 top: this.leftBlank[1], 409 bottom: this.leftBlank[3] 410 }) 411 412 if (this.browserController.isBrowserShow) { 413 Column() { 414 PhotoBrowserComponent({ 415 pageStatus: this.pageStatus, 416 geometryTransitionEnable: true, 417 isRunningAnimation: $isRunningAnimation, 418 browserController: this.browserController 419 }) 420 } 421 .width('100%') 422 .height('100%') 423 424 // Opacity must change for TransitionEffect taking effect 425 .transition(TransitionEffect.asymmetric(TransitionEffect.opacity(0.99), TransitionEffect.opacity(0.99))) 426 } 427 428 if (this.browserController.isSelectBrowserShow) { 429 Column() { 430 SelectPhotoBrowserView({ 431 pageStatus: this.pageStatus, 432 geometryTransitionEnable: true, 433 isRunningAnimation: $isRunningAnimation, 434 browserController: this.browserController 435 }) 436 } 437 .width('100%') 438 .height('100%') 439 440 // Opacity must change for TransitionEffect taking effect 441 .transition(TransitionEffect.asymmetric(TransitionEffect.opacity(0.99), TransitionEffect.opacity(0.99))) 442 } 443 } 444 } 445 446 pageTransition() { 447 PageTransitionEnter({ type: RouteType.Pop, duration: 300 }) 448 .opacity(1) 449 PageTransitionExit({ type: RouteType.Push, duration: 300 }) 450 .opacity(1) 451 } 452 453 private updateAnimationStatus() { 454 this.isRunningAnimation = this.browserController.isAnimating; 455 } 456 457 private getGeometryTransitionId(item: ViewData, index: number): string { 458 let mediaItem = item.mediaItem as MediaItem; 459 if (mediaItem) { 460 return TAG + mediaItem.getHashCode() + (this.mSelectManager?.isItemSelected(mediaItem.uri as string) ?? false); 461 } else { 462 return TAG + item.viewIndex; 463 } 464 } 465 466 private initGridRowCount(): void { 467 let contentWidth = ScreenManager.getInstance().getWinWidth(); 468 let margin = 0; 469 let maxThumbWidth = px2vp(Constants.GRID_IMAGE_SIZE) * Constants.GRID_MAX_SIZE_RATIO; 470 this.gridRowCount = Math.max(Math.round(((contentWidth - Constants.NUMBER_2 * margin) + 471 Constants.GRID_GUTTER) / (maxThumbWidth + Constants.GRID_GUTTER)), 472 Constants.DEFAULT_ALBUM_GRID_COLUMN_MIN_COUNT); 473 Log.info(TAG, `initGridRowCount contentWidth: ${contentWidth}`); 474 } 475}