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 { MenuOperation, WindowUtil } from '@ohos/common'; 17import { 18 Action, 19 AlbumInfo, 20 AlbumSetDataInfo, 21 AlbumSetDataSource, 22 AlbumSetSelectManager, 23 BroadCast, 24 BroadCastConstants, 25 BroadCastManager, 26 CommonObserverCallback, 27 Constants, 28 Log, 29 MediaObserver, 30 MenuContext, 31 MenuOperationFactory, 32 ScreenManager, 33 TraceControllerUtils, 34 UiUtil 35} from '@ohos/common'; 36import { 37 AlbumSetNewMenuOperation, 38 CustomDialogView, 39 MoveOrCopyBroadCastProp, 40 NoPhotoIndexComponent, 41 TabItemWithText 42} from '@ohos/common/CommonComponents'; 43import { TimelineDataSourceManager } from '../../../../../../../timeline'; 44import { AlbumSetPageActionBar } from './AlbumSetPageActionBar'; 45import { AlbumSetPageToolBar } from './AlbumSetPageToolBar'; 46import { AlbumGridItemNewStyle } from './AlbumGridItemNewStyle'; 47import { AlbumSetDeleteMenuOperation } from './AlbumSetDeleteMenuOperation'; 48import { AlbumSetRenameMenuOperation } from './AlbumSetRenameMenuOperation'; 49 50const TAG: string = 'AlbumSetPage'; 51const TRANSITION_EFFECT_ALPHA: number = 0.99; 52 53// Album Set Page 54@Component 55export struct AlbumSetPage { 56 @Consume @Watch('onModeChange') isAlbumSetSelectedMode: boolean; 57 @Provide('selectedCount') @Watch('updateRightClickMenuList') selectedAlbumsCount: number = 0; 58 @Provide @Watch('updateRightClickMenuList') isDisableDelete: boolean = false; 59 @Provide @Watch('updateRightClickMenuList') isDisableRename: boolean = false; 60 @State isEmpty: boolean = TimelineDataSourceManager.getInstance().getDataSource().isEmpty(); 61 @Provide gridColumnsCount: number = 0; 62 @Provide broadCast: BroadCast = new BroadCast(); 63 @Consume @Watch('onIndexPageShow') isShow: boolean; 64 albums: AlbumSetDataSource = new AlbumSetDataSource(this.broadCast); 65 appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 66 isInCurrentTab = false; 67 isActive = false; // Whether the page is in the foreground 68 scroller: Scroller = new Scroller(); 69 @Consume('isShowSideBar') @Watch('initGridRowCount') isSidebar: boolean; 70 mSelectManager = new AlbumSetSelectManager(); 71 isDataFreeze: boolean = false; 72 // the switch of distributed page 73 @Provide isTabBarShow: boolean = false; 74 @Provide rightClickMenuList: Action[] = []; 75 @StorageLink('isHorizontal') isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 76 @StorageLink('statusBarHeight') statusBarHeight: number = 0; 77 @StorageLink('deviceType') deviceType: string | undefined = AppStorage.get<string>('deviceType'); 78 @State pageStatus: boolean = false; 79 @State bottomHeight: number = 0; 80 @StorageLink('selectedAlbumUri') selectedAlbumUri: string = ''; 81 @StorageLink('albumActionBarOpacity') albumActionBarOpacity: number = 1; 82 @StorageLink('albumOpacity') albumOpacity: number = 1; 83 @StorageLink('albumOtherScale') albumOtherScale: number = 1; 84 private tabs: TabItemWithText[] = [ 85 new TabItemWithText($r('app.string.local'), false), 86 new TabItemWithText($r('app.string.other_equipment'), false) 87 ]; 88 private dataObserver: CommonObserverCallback = new CommonObserverCallback(this); 89 private tabsController: TabsController = new TabsController(); 90 private currentIndex: number = Constants.LOCAL_TAB_INDEX; 91 private needNotify = false; 92 private ignoreLocalNotify = false; 93 private minGridColumnsCount: number = 2; 94 private onWinSizeChangedFunc: Function = (): void => this.initGridRowCount(); 95 private onTabChangedFunc: Function = (index: number): void => this.onTabChanged(index); 96 private onStateResetFunc: Function = (index: number): void => this.onStateReset(index); 97 private onSendMoveCopyBroadCastFunc: Function = (index: number): void => this.onSendMoveCopyBroadCast(index); 98 private onLoadingFinishedFunc: Function = (size: number): void => this.onLoadingFinished(size); 99 private onResetZeroFunc: Function = (pageNumber: number): void => this.onResetZero(pageNumber); 100 private onMenuClickedFunc = (action: Action, arg: Object[]): void => this.onMenuClicked(action, arg); 101 private selectFunc = ( 102 key: string, value: boolean, isDisableRename: boolean, isDisableDelete: boolean, callback: Function): void => 103 this.select(key, value, isDisableRename, isDisableDelete, callback); 104 105 onMenuClicked(action: Action, arg: Object[]) { 106 Log.info(TAG, `onMenuClicked, action: ${action.actionID}`); 107 let menuContext: MenuContext; 108 let menuOperation: MenuOperation; 109 if (action.actionID === Action.NEW.actionID) { 110 menuContext = new MenuContext(); 111 menuContext 112 .withOperationStartCallback((): void => this.onOperationStart()) 113 .withOperationEndCallback((): void => this.onOperationEnd()) 114 .withAlbumSetDataSource(this.albums) 115 .withBroadCast(this.broadCast); 116 menuOperation = 117 MenuOperationFactory.getInstance().createMenuOperation(AlbumSetNewMenuOperation, menuContext); 118 menuOperation.doAction(); 119 } else if (action.actionID === Action.CANCEL.actionID) { 120 this.isAlbumSetSelectedMode = false; 121 } else if (action.actionID === Action.MULTISELECT.actionID) { 122 this.isAlbumSetSelectedMode = true; 123 } else if (action.actionID === Action.RENAME.actionID) { 124 menuContext = new MenuContext(); 125 menuContext 126 .withFromSelectMode(true) 127 .withSelectManager(this.mSelectManager) 128 .withOperationStartCallback((): void => this.onOperationStart()) 129 .withOperationEndCallback((): void => this.onOperationEnd()) 130 .withBroadCast(this.broadCast); 131 menuOperation = 132 MenuOperationFactory.getInstance().createMenuOperation(AlbumSetRenameMenuOperation, menuContext); 133 menuOperation.doAction(); 134 } else if (action.actionID === Action.DELETE.actionID) { 135 menuContext = new MenuContext(); 136 menuContext 137 .withFromSelectMode(true) 138 .withSelectManager(this.mSelectManager) 139 .withOperationStartCallback((): void => this.onOperationStart()) 140 .withOperationEndCallback((): void => this.onOperationEnd()) 141 .withBroadCast(this.broadCast); 142 menuOperation = 143 MenuOperationFactory.getInstance().createMenuOperation(AlbumSetDeleteMenuOperation, menuContext); 144 menuOperation.doAction(); 145 } 146 } 147 148 onOperationStart(): void { 149 this.isDataFreeze = true; 150 this.ignoreLocalNotify = true; 151 this.albums.freeze(); 152 } 153 154 onOperationEnd(): void { 155 Log.debug(TAG, `onOperationEnd`); 156 this.isDataFreeze = false; 157 this.isAlbumSetSelectedMode = false 158 this.ignoreLocalNotify = false; 159 this.albums.onChange('image'); 160 this.albums.unfreeze(); 161 } 162 163 aboutToAppear(): void { 164 TraceControllerUtils.startTrace('AlbumSetPageAboutToAppear'); 165 Log.info(TAG, `AlbumSetPageAboutToAppear`); 166 this.isEmpty = (this.albums.totalCount() == 0); 167 this.appBroadCast.on(BroadCastConstants.ON_TAB_CHANGED, this.onTabChangedFunc); 168 this.appBroadCast.on(BroadCastConstants.RESET_STATE_EVENT, this.onStateResetFunc); 169 this.appBroadCast.on(BroadCastConstants.SEND_COPY_OR_MOVE_BROADCAST, this.onSendMoveCopyBroadCastFunc); 170 AppStorage.setOrCreate('setSelectManagerToAnother', this.mSelectManager); 171 this.broadCast.on(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc); 172 this.appBroadCast.on(BroadCastConstants.RESET_ZERO, this.onResetZeroFunc); 173 174 MediaObserver.getInstance().registerObserver(this.dataObserver); 175 176 this.onActive(); 177 178 this.updateRightClickMenuList(); 179 this.initGridRowCount(); 180 // 后续phone缩略图支持横竖屏后再放开 181 if (AppStorage.get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 182 ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWinSizeChangedFunc); 183 } 184 185 let self = this; 186 this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc); 187 this.mSelectManager.registerCallback('updateCount', (newState: number) => { 188 Log.info(TAG, `updateSelectedCount ${newState}`); 189 if (this.isDataFreeze) { 190 return; 191 } 192 if (this.selectedAlbumsCount === 0 && newState === 0) { 193 this.selectedAlbumsCount--; 194 } else { 195 this.selectedAlbumsCount = newState; 196 } 197 }); 198 this.mSelectManager.registerCallback('updateToolBarState', 199 (isDisableRename: boolean, isDisableDelete: boolean) => { 200 if (this.isDataFreeze) { 201 return; 202 } 203 Log.info(TAG, `updateToolBarState:\ 204 isDisableRename: ${isDisableRename}, isDisableDelete: ${isDisableDelete}`); 205 this.isDisableRename = isDisableRename 206 this.isDisableDelete = isDisableDelete 207 } 208 ); 209 210 if (Constants.LOCAL_TAB_INDEX == this.currentIndex) { 211 this.tabs[Constants.LOCAL_TAB_INDEX].isSelected = true; 212 this.tabs[Constants.OTHER_EQUIPMENT_TAB_INDEX].isSelected = false; 213 } else { 214 this.tabs[Constants.LOCAL_TAB_INDEX].isSelected = false; 215 this.tabs[Constants.OTHER_EQUIPMENT_TAB_INDEX].isSelected = true; 216 } 217 TraceControllerUtils.finishTrace('AlbumSetPageAboutToAppear'); 218 } 219 220 aboutToDisappear(): void { 221 this.onInActive(); 222 this.broadCast.off(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc); 223 this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc); 224 this.appBroadCast.off(BroadCastConstants.ON_TAB_CHANGED, this.onTabChangedFunc); 225 this.appBroadCast.off(BroadCastConstants.RESET_STATE_EVENT, this.onStateResetFunc); 226 this.appBroadCast.off(BroadCastConstants.SEND_COPY_OR_MOVE_BROADCAST, this.onSendMoveCopyBroadCastFunc); 227 this.appBroadCast.off(BroadCastConstants.RESET_ZERO, this.onResetZeroFunc); 228 MediaObserver.getInstance().unregisterObserver(this.dataObserver); 229 this.dataObserver.clearSource(); 230 // 后续phone缩略图支持横竖屏后再放开 231 if (AppStorage.get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 232 ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWinSizeChangedFunc); 233 } 234 } 235 236 // Callback when the page is show. 237 onIndexPageShow() { 238 Log.info(TAG, `[onIndexPageShow] isShow=${this.isShow}, isInCurrentTab=${this.isInCurrentTab}`); 239 if (this.isShow && this.isInCurrentTab) { 240 this.onActive(); 241 } else if (!this.isShow && this.isInCurrentTab) { 242 this.onInActive(); 243 } else { 244 } 245 } 246 247 onModeChange() { 248 Log.info(TAG, `onModeChange ${this.isAlbumSetSelectedMode}`); 249 this.updateRightClickMenuList(); 250 if (!this.isAlbumSetSelectedMode) { 251 this.mSelectManager.emitCallback('updateCount', [0]); 252 this.mSelectManager.onModeChange(false); 253 } 254 } 255 256 updateRightClickMenuList() { 257 if (this.isAlbumSetSelectedMode) { 258 this.rightClickMenuList = []; 259 if (!this.isDisableRename && this.selectedAlbumsCount == 1) { 260 this.rightClickMenuList.push(Action.RENAME); 261 } 262 if (!this.isDisableDelete && this.selectedAlbumsCount > 0) { 263 this.rightClickMenuList.push(Action.DELETE); 264 } 265 } 266 } 267 268 onStateReset(index: number): void { 269 if (index == Constants.ALBUM_PAGE_INDEX) { 270 this.isAlbumSetSelectedMode = false; 271 } 272 } 273 274 onTabChanged(index: number): void { 275 if (index == Constants.ALBUM_PAGE_INDEX) { 276 this.isInCurrentTab = true; 277 this.onActive(); 278 } else { 279 this.isInCurrentTab = false; 280 this.isAlbumSetSelectedMode = false; 281 this.onInActive(); 282 } 283 } 284 285 onLoadingFinished(size: number): void { 286 this.isEmpty = (size == 0); 287 } 288 289 select(key: string, value: boolean, isDisableRename: boolean, isDisableDelete: boolean, callback: Function): void { 290 this.mSelectManager.toolBarStateToggle(key, value, isDisableRename, isDisableDelete); 291 if (this.mSelectManager.toggle(key, value)) { 292 Log.info(TAG, 'enter event process'); 293 callback(); 294 } 295 } 296 297 onSendMoveCopyBroadCast(index: number): void { 298 if (index == Constants.ALBUM_PAGE_INDEX) { 299 MoveOrCopyBroadCastProp.getInstance().sendMoveOrAddBroadCast(this.broadCast); 300 } 301 } 302 303 // Callback when the page is in the foreground 304 onActive() { 305 if (!this.isActive) { 306 Log.info(TAG, 'onActive'); 307 this.isActive = true; 308 if (this.currentIndex == Constants.LOCAL_TAB_INDEX) { 309 this.onLocalAlbumSetActive(); 310 } else { 311 this.onLocalAlbumSetInActive(); 312 } 313 this.showNotify(); 314 if (this.albums.totalCount() == 0) { 315 this.albums.loadData(); 316 } 317 } 318 } 319 320 // Callback when the page is in the background 321 onInActive() { 322 if (this.isActive) { 323 Log.info(TAG, 'onInActive'); 324 this.isActive = false; 325 this.albums && this.albums.onInActive(); 326 } 327 } 328 329 // Callback when the local albums' page is in the foreground 330 onLocalAlbumSetActive() { 331 if (this.currentIndex == Constants.LOCAL_TAB_INDEX) { 332 Log.info(TAG, 'Local album set is on active'); 333 this.albums && this.albums.onActive(); 334 } 335 } 336 337 // Callback when the local albums' page is in the background 338 onLocalAlbumSetInActive() { 339 if (this.currentIndex == Constants.OTHER_EQUIPMENT_TAB_INDEX) { 340 Log.info(TAG, 'Local album set is on inactive'); 341 this.albums && this.albums.onInActive(); 342 } 343 } 344 345 onResetZero(pageNumber: number) { 346 if (pageNumber == Constants.ALBUM_PAGE_INDEX) { 347 this.scroller.scrollEdge(Edge.Top); 348 } 349 } 350 351 initGridRowCount(): void { 352 Log.info(TAG, `get screen width is : ${ScreenManager.getInstance().getWinWidth()}`); 353 Log.info(TAG, `get screen height is : ${ScreenManager.getInstance().getWinHeight()}`); 354 let currentBreakpoint = AppStorage.get<string>('currentBreakpoint'); 355 if (currentBreakpoint === Constants.BREAKPOINT_LG && this.deviceType == Constants.PAD_DEVICE_TYPE) { 356 this.gridColumnsCount = UiUtil.getAlbumGridCount(true); 357 } else { 358 this.gridColumnsCount = UiUtil.getAlbumGridCount(this.isSidebar); 359 } 360 Log.info(TAG, `the grid count in a line is: ${this.gridColumnsCount}`); 361 } 362 363 onMediaLibDataChange(changeType: string): void { 364 Log.info(TAG, `onMediaLibDataChange type: ${changeType}`); 365 if (!this.ignoreLocalNotify) { 366 this.albums.onChange(changeType); 367 } 368 } 369 370 isRecycleAlbumOfPhoneLikeDevice(item: AlbumInfo): boolean { 371 return this.deviceType != Constants.PC_DEVICE_TYPE && item.isTrashAlbum; 372 } 373 374 @Builder 375 LocalAlbumSet() { 376 Stack() { 377 if (!this.isEmpty) { 378 Grid(this.scroller) { 379 LazyForEach(this.albums, (item: AlbumSetDataInfo, index?: number) => { 380 GridItem() { 381 if (this.selectedAlbumUri === item.data.uri) { 382 Column() { 383 } 384 .width('100%') 385 .height(AppStorage.get<string>(Constants.KEY_OF_ALBUM_WIDTH) as string) 386 .key('' + index) 387 } else { 388 AlbumGridItemNewStyle({ 389 item: item.data, 390 isSelected: this.isAlbumSetSelectedMode ? 391 this.mSelectManager.isItemSelected(item.data.uri) : false, 392 onMenuClicked: this.onMenuClickedFunc, 393 onMenuClickedForSingleItem: (action: Action, currentAlbum: AlbumInfo): void => 394 this.onMenuClickedForSingleItem(action, currentAlbum), 395 keyIndex: index, 396 bottomHeight: $bottomHeight 397 }) 398 .transition(TransitionEffect.opacity(TRANSITION_EFFECT_ALPHA)) 399 } 400 } 401 .margin({ 402 bottom: this.bottomHeight 403 }) 404 .clip(false) 405 .key('Album_' + index) 406 }, (item: AlbumSetDataInfo) => { 407 if (item.data.mediaItem) { 408 return item.data.getHashCode() + item.data.mediaItem.getHashCode(); 409 } 410 return item.data.getHashCode(); 411 }) 412 } 413 .edgeEffect(EdgeEffect.Spring) 414 .clip(false) 415 .columnsTemplate('1fr '.repeat(this.gridColumnsCount)) 416 .padding({ 417 top: $r('app.float.album_set_page_padding_top'), 418 bottom: this.isHorizontal ? 0 : $r('app.float.tab_bar_vertical_height'), 419 left: $r('sys.float.ohos_id_card_margin_start'), 420 right: $r('sys.float.ohos_id_card_margin_end') 421 }) 422 .columnsGap($r('sys.float.ohos_id_card_margin_middle')) 423 .rowsGap($r('sys.float.ohos_id_elements_margin_vertical_l')) 424 .scrollBar(BarState.Auto) 425 } 426 } 427 } 428 429 build() { 430 Stack() { 431 Flex({ 432 direction: FlexDirection.Column, 433 justifyContent: FlexAlign.Start, 434 alignItems: ItemAlign.Start 435 }) { 436 if (!this.isEmpty || this.isTabBarShow) { 437 Column() { 438 AlbumSetPageActionBar({ onMenuClicked: this.onMenuClickedFunc }) 439 .opacity(this.albumActionBarOpacity) 440 } 441 .backgroundColor($r('app.color.default_background_color')) 442 Column() { 443 this.LocalAlbumSet() 444 } 445 .zIndex(Constants.NEGATIVE_1) 446 .opacity(this.albumOpacity) 447 .scale({ x: this.albumOtherScale, y: this.albumOtherScale }) 448 .layoutWeight(Constants.NUMBER_1) 449 } 450 if (this.isAlbumSetSelectedMode) { 451 AlbumSetPageToolBar({ onMenuClicked: this.onMenuClickedFunc }) 452 } 453 454 if (this.isEmpty && !this.isTabBarShow) { 455 NoPhotoIndexComponent({ index: Constants.ALBUM_PAGE_INDEX, hasBarSpace: true }) 456 } 457 } 458 459 CustomDialogView({ broadCast: $broadCast }) 460 } 461 } 462 463 private onMenuClickedForSingleItem(action: Action, currentAlbum: AlbumInfo) { 464 Log.info(TAG, `single menu click, action: ${action?.actionID}, currentUri: ${currentAlbum?.albumName}`); 465 if (currentAlbum == undefined) { 466 return; 467 } 468 let menuContext: MenuContext; 469 let menuOperation: MenuOperation; 470 if (action.actionID === Action.RENAME.actionID) { 471 menuContext = new MenuContext(); 472 menuContext 473 .withFromSelectMode(false) 474 .withAlbumInfo(currentAlbum) 475 .withOperationStartCallback((): void => this.onOperationStart()) 476 .withOperationEndCallback((): void => this.onOperationEnd()) 477 .withBroadCast(this.broadCast); 478 menuOperation = MenuOperationFactory.getInstance() 479 .createMenuOperation(AlbumSetRenameMenuOperation, menuContext); 480 menuOperation.doAction(); 481 } else if (action.actionID === Action.DELETE.actionID) { 482 menuContext = new MenuContext(); 483 menuContext 484 .withFromSelectMode(false) 485 .withAlbumInfo(currentAlbum) 486 .withOperationStartCallback((): void => this.onOperationStart()) 487 .withOperationEndCallback((): void => this.onOperationEnd()) 488 .withBroadCast(this.broadCast); 489 menuOperation = MenuOperationFactory.getInstance() 490 .createMenuOperation(AlbumSetDeleteMenuOperation, menuContext); 491 menuOperation.doAction(); 492 } 493 } 494 495 private showNotify() { 496 if (this.needNotify) { 497 UiUtil.showToast($r('app.string.distributed_album_disconnected')); 498 this.needNotify = false; 499 } 500 } 501 502 private onDistributedTabChanged(index: number) { 503 this.currentIndex = index; 504 if (index == Constants.LOCAL_TAB_INDEX) { 505 this.onLocalAlbumSetActive(); 506 } else { 507 this.onLocalAlbumSetInActive(); 508 } 509 } 510 511 private onUpdateRemoteDevice(res: string, deviceId: string, size: number): void { 512 Log.info(TAG, `onUpdateRemoteDevice size: ${size} deviceId: ${deviceId} type: ${res}`); 513 514 if (res == 'offline') { 515 this.needNotify = true; 516 } 517 518 if (size <= 0) { 519 this.currentIndex = Constants.LOCAL_TAB_INDEX 520 try { 521 this.tabsController.changeIndex(this.currentIndex); 522 } catch (error) { 523 Log.debug(TAG, `change tab index failed: ${error}`); 524 } 525 this.tabs[Constants.LOCAL_TAB_INDEX].isSelected = true; 526 this.tabs[Constants.OTHER_EQUIPMENT_TAB_INDEX].isSelected = false; 527 528 if (this.isActive) { 529 this.showNotify(); 530 } 531 } 532 533 this.isTabBarShow = (size > 0); 534 } 535} 536