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