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 Curves from '@ohos.curves'; 17import { MenuOperation, WindowUtil } from '@ohos/common'; 18import { 19 Action, 20 AddMenuOperation, 21 BatchDeleteMenuOperation, 22 BroadCast, 23 BroadCastConstants, 24 BroadCastManager, 25 Constants, 26 DateUtil, 27 DeleteMenuOperation, 28 Log, 29 MediaItem, 30 MediaOperationType, 31 MenuContext, 32 MenuOperationFactory, 33 ScreenManager, 34 ShareMenuOperation, 35 TimelineData, 36 TimelineSelectManager, 37 TraceControllerUtils, 38 UiUtil, 39 ViewData, 40 ViewType 41} from '@ohos/common'; 42import { 43 BrowserController, 44 CustomDialogView, 45 ImageGridItemComponent, 46 NoPhotoIndexComponent, 47} from '@ohos/common/CommonComponents'; 48import { TimelineDataSource } from '../model/TimelineDataSource'; 49import { TimelineTitleComponent } from './TimelineTitleComponent'; 50import { TimelinePageActionBar } from './TimelinePageActionBar'; 51import { TimelinePageToolBar } from './TimelinePageToolBar'; 52import router from '@ohos.router'; 53import { TimelineDataSourceManager } from '../model/TimelineDataSourceManager'; 54import { TimelineScrollBar } from './TimelineScrollBar'; 55 56const TAG: string = 'TimelinePage'; 57AppStorage.setOrCreate('timelinePageIndex', Constants.INVALID); 58 59interface Params { 60 albumName: string; 61 albumUri: string; 62 pageType: string; 63 pageFrom: number; 64}; 65 66// PHOTO Page 67@Component 68export struct TimelinePage { 69 @Provide isEmpty: boolean = false; 70 @State isShowScrollBar: boolean = false; 71 @StorageLink('isHorizontal') isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 72 @State gridRowCount: number = 0; 73 @Consume @Watch('updateRightClickMenuList') isSelectedMode: boolean; 74 @Consume('isShowSideBar') @Watch('initGridRowCount') isSidebar: boolean; 75 @Provide isAllSelected: boolean = false; 76 @State totalSelectedCount: number = 0; 77 @Provide broadCast: BroadCast = TimelineDataSourceManager.getInstance().getBroadCast(); 78 @Consume @Watch('onIndexPageShow') isShow: boolean; 79 @StorageLink('timelinePageIndex') @Watch('onIndexChange') timelinePageIndex: number = Constants.INVALID; 80 @StorageLink('isSplitMode') isSplitMode: boolean = ScreenManager.getInstance().isSplitMode(); 81 @StorageLink('leftBlank') leftBlank: number[] = 82 [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()]; 83 dataSource: TimelineDataSource | null = null; 84 mSelectManager: TimelineSelectManager = new TimelineSelectManager(); 85 scroller: Scroller = new Scroller(); 86 appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 87 isInCurrentTab = true; // Is it on the current tab page 88 isDataFreeze = false; // Is the page data frozen 89 deleteMode = false; // Is delete mode 90 isActive = false; // Is the page active 91 routerStart = false; // Is move or copy router page 92 @Provide moreMenuList: Action[] = []; 93 @Provide rightClickMenuList: Action[] = []; 94 @State groupSelectMode: boolean[] = []; 95 @Provide yearData: TimelineData[] = []; 96 @Provide dateText: string = ''; 97 @Provide isShowBar: boolean = true; 98 @StorageLink('placeholderIndex') @Watch('onPlaceholderChanged') placeholderIndex: number = -1; 99 @ObjectLink browserController: BrowserController; 100 @Provide hidePopup: boolean = false; 101 // 选择模式下,鼠标对着未勾选项按右键弹框时,移动和复制菜单点击事件的标识位 102 private isMvOrCpSeparatesItem: boolean = false; 103 private mvOrCpSeparatesItem?: MediaItem; 104 private onWindowSizeChangeCallBack: Function = (): void => this.initGridRowCount(); 105 private backPressEventFunc: Function = (callback: Function): void => this.onIndexBackPress(callback); 106 private onTableChangedFunc: Function = (index: number): void => this.onTabChanged(index); 107 private resetStateEventFunc: Function = (index: number): void => this.onStateReset(index); 108 private updateDataSourceFunc: Function = (item: MediaItem): void => this.onUpdateFavorState(item); 109 private resetZeroFunc: Function = (pageNumber: number): void => this.resetZero(pageNumber); 110 private onLoadingFinishedFunc: Function = (size: number): void => this.onLoadingFinished(size); 111 private selectFunc: Function = (index: number, id: string, isSelected: boolean, callback?: Function): void => 112 this.select(index, id, isSelected, callback); 113 private groupSelectFunc: Function = (position: number): void => this.groupSelect(position); 114 private jumpPhotoBrowserFunc: Function = (name: string, item: MediaItem, 115 geometryTapIndex: number, geometryTransitionString: string): void => 116 this.jumpPhotoBrowser(name, item, geometryTapIndex, geometryTransitionString); 117 private jumpThirdPhotoBrowserFunc: Function = (name: string, item: MediaItem, 118 geometryTapIndex: number, geometryTransitionString: string): void => 119 this.jumpThirdPhotoBrowser(name, item, geometryTapIndex, geometryTransitionString); 120 private onDataReloadedFunc: Function = (): void => this.onDataReloaded(); 121 private initDateTextFunc: Function = (): void => this.initDateText(); 122 @State layoutOptions: GridLayoutOptions = { 123 regularSize: [1, 1], 124 irregularIndexes: [], 125 } 126 127 onPlaceholderChanged() { 128 Log.debug(TAG, 'onPlaceholderChanged placeholderIndex is ' + this.placeholderIndex); 129 if (this.placeholderIndex != -1) { 130 this.scroller.scrollToIndex(this.placeholderIndex); 131 } 132 } 133 134 aboutToAppear(): void { 135 TraceControllerUtils.startTrace('TimelinePageAboutToAppear'); 136 Log.info(TAG, 'aboutToAppear begin'); 137 let self = this; 138 this.dataSource = TimelineDataSourceManager.getInstance().getDataSource(); 139 let params: Params = router.getParams() as Params; 140 if (params != null && params.pageFrom && params.pageFrom == Constants.ENTRY_FROM.CAMERA) { 141 this.dataSource.initData(); 142 } 143 this.mSelectManager.setGroupData(this.dataSource.getGroupData()); 144 this.mSelectManager.setTotalCount(this.dataSource.getMediaCount()); 145 this.moreMenuList = [Action.ADD, Action.INFO]; 146 this.updateRightClickMenuList(); 147 this.dataSource.registerCallback('updateGroupData', (newState: TimelineData[]) => { 148 self.mSelectManager.updateGroupData(newState); 149 self.updateYearMap(); 150 self.updateLayoutOptions(newState); 151 }); 152 this.dataSource.registerCallback('updateCount', (newState: number) => { 153 Log.info(TAG, `updateCount ${newState}`); 154 self.isEmpty = !Boolean(newState); 155 self.isShowScrollBar = (newState > Constants.PHOTOS_CNT_FOR_HIDE_SCROLL_BAR); 156 self.mSelectManager.setTotalCount(newState); 157 }); 158 159 // 后续phone缩略图支持横竖屏后再放开 160 if (AppStorage.Get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) { 161 ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 162 } 163 164 this.appBroadCast.on(BroadCastConstants.BACK_PRESS_EVENT, this.backPressEventFunc); 165 this.appBroadCast.on(BroadCastConstants.ON_TAB_CHANGED, this.onTableChangedFunc); 166 this.appBroadCast.on(BroadCastConstants.RESET_STATE_EVENT, this.resetStateEventFunc); 167 this.appBroadCast.on(BroadCastConstants.UPDATE_DATA_SOURCE, this.updateDataSourceFunc); 168 this.appBroadCast.on(BroadCastConstants.RESET_ZERO, this.resetZeroFunc); 169 this.broadCast.on(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc); 170 171 Log.info(TAG, 'aboutToAppear doing'); 172 this.mSelectManager.setPhotoDataImpl(); 173 174 this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc); 175 this.broadCast.on(BroadCastConstants.GROUP_SELECT, this.groupSelectFunc); 176 this.broadCast.on(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc); 177 this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 178 this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadedFunc); 179 this.broadCast.on(BroadCastConstants.INIT_DATE_TEXT, this.initDateTextFunc); 180 181 this.mSelectManager.registerCallback('allSelect', (newState: boolean) => { 182 Log.info(TAG, `allSelect ${newState}`); 183 if (this.isDataFreeze) { 184 return; 185 } 186 this.isAllSelected = newState; 187 if (this.dataSource != null) { 188 this.dataSource.forceUpdate(); 189 } 190 }); 191 192 this.mSelectManager.registerCallback('updateGroupCount', () => { 193 Log.info(TAG, 'updateGroupCount'); 194 if (this.isDataFreeze) { 195 return; 196 } 197 this.updateGroupSelectMode(); 198 }); 199 200 this.mSelectManager.registerCallback('updateCount', (newState: number) => { 201 Log.info(TAG, `mSelectManager updateCount ${newState}`); 202 if (this.isDataFreeze) { 203 return; 204 } 205 this.totalSelectedCount = newState; 206 this.moreMenuList = Boolean(newState) ? [Action.ADD, Action.INFO] 207 : [Action.ADD_INVALID, Action.INFO_INVALID]; 208 }); 209 this.initGridRowCount(); 210 this.updateGroupSelectMode(); 211 this.updateYearMap(); 212 this.onActive(); 213 this.updateLayoutOptions(this.dataSource.groups); 214 TraceControllerUtils.finishTrace('TimelinePageAboutToAppear'); 215 } 216 217 updateLayoutOptions(groups: TimelineData[]): void { 218 this.layoutOptions.irregularIndexes && this.layoutOptions.irregularIndexes.pop(); 219 let currentTitleIndex = 0; 220 let count: number[] = []; 221 count.push(currentTitleIndex); 222 for (let index = 0; index < groups.length - 1; index++) { 223 currentTitleIndex = currentTitleIndex + groups[index].count + 1; 224 count.push(currentTitleIndex); 225 } 226 this.layoutOptions = { 227 regularSize: [1, 1], 228 irregularIndexes: count, 229 }; 230 } 231 232 private onLoadingFinished(size: number): void { 233 Log.info(TAG, `ON_LOADING_FINISHED size: ${size}`); 234 } 235 236 private select(index: number, id: string, isSelected: boolean, callback?: Function): void { 237 if (this.mSelectManager.toggle(id, isSelected, index)) { 238 if (!this.isSelectedMode) { 239 this.isSelectedMode = true; 240 } 241 } 242 if (callback) { 243 callback(); 244 } 245 } 246 247 private groupSelect(position: number): void { 248 Log.info(TAG, `GROUP_SELECT ${position}`); 249 if (this.mSelectManager.toggleGroup(this.mSelectManager.getTitleCoordinate(position))) { 250 this.totalSelectedCount = this.mSelectManager.getSelectedCount(); 251 } 252 } 253 254 private jumpPhotoBrowser(name: string, item: MediaItem, 255 geometryTapIndex: number, geometryTransitionString: string): void { 256 let targetIndex = this.dataSource == null ? Constants.NOT_FOUND : this.dataSource.getDataIndex(item); 257 if (targetIndex == Constants.NOT_FOUND) { 258 Log.error(TAG, 'targetIndex is not found'); 259 return; 260 } 261 AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource); 262 if (geometryTapIndex !== undefined && geometryTransitionString !== undefined) { 263 this.jumpToPhotoBrowserGeometryTransition(targetIndex, name, item, geometryTapIndex, geometryTransitionString); 264 } else { 265 this.jumpToPhotoBrowserNormal(targetIndex, name, item); 266 } 267 } 268 269 private jumpThirdPhotoBrowser(name: string, item: MediaItem, 270 geometryTapIndex: number, geometryTransitionString: string): void { 271 let targetIndex = this.dataSource == null ? Constants.NOT_FOUND : this.dataSource.getDataIndex(item); 272 if (targetIndex == Constants.NOT_FOUND) { 273 Log.error(TAG, 'targetIndex is not found'); 274 return; 275 } 276 Log.info(TAG, `JUMP_THIRD_PHOTO_BROWSER.index: ${targetIndex} transition: ${name}`); 277 AppStorage.setOrCreate(Constants.PHOTO_GRID_SELECT_MANAGER, this.mSelectManager); 278 AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource); 279 if (geometryTapIndex !== undefined && geometryTransitionString !== undefined) { 280 this.jumpToSelectPhotoBrowserGeometryTransition( 281 targetIndex, name, item, geometryTapIndex, geometryTransitionString); 282 } else { 283 this.jumpToSelectPhotoBrowserNormal(targetIndex, name, item); 284 } 285 } 286 287 private onDataReloaded(): void { 288 Log.info(TAG, 'ON_DATA_RELOADED'); 289 if (this.deleteMode) { 290 animateTo({ 291 duration: 300, // 删除动画时长 292 curve: Curves.cubicBezier(0.0, 0.0, 0.2, 1.0) // 减速曲线参数 293 }, (): void => { 294 this.dataSource?.onDataReloaded(); 295 }) 296 this.deleteMode = false; 297 } else { 298 if (this.dataSource != null) { 299 this.dataSource.onDataReloaded(); 300 } 301 } 302 } 303 304 private initDateText(): void { 305 let scrollMediaItem: MediaItem = this.dataSource == null ? new MediaItem() : 306 this.dataSource.getMediaItemByPosition(0) as MediaItem; 307 this.dateText = DateUtil.getLocalizedYearAndMonth(scrollMediaItem.getDataTaken()); 308 } 309 310 jumpToPhotoBrowserNormal(targetIndex: number, name: string, item: MediaItem) { 311 router.pushUrl({ 312 url: 'pages/PhotoBrowser', 313 params: { 314 position: targetIndex, 315 transition: name, 316 leftBlank: this.leftBlank, 317 } 318 }); 319 } 320 321 jumpToPhotoBrowserGeometryTransition(targetIndex: number, name: string, item: MediaItem, geometryTapIndex: number, 322 geometryTransitionString: string) { 323 interface Msg { 324 position: number; 325 transition: string; 326 leftBlank: number[]; 327 } 328 329 const params: Msg = { 330 position: targetIndex, 331 transition: name, 332 leftBlank: this.leftBlank, 333 }; 334 this.browserController.showBrowser(geometryTapIndex, geometryTransitionString, TAG, params); 335 } 336 337 jumpToSelectPhotoBrowserNormal(targetIndex: number, name: string, item: MediaItem) { 338 router.pushUrl({ 339 url: 'pages/SelectPhotoBrowser', 340 params: { 341 position: targetIndex, 342 transition: name, 343 } 344 }); 345 } 346 347 jumpToSelectPhotoBrowserGeometryTransition(targetIndex: number, name: string, item: MediaItem, 348 geometryTapIndex: number, geometryTransitionString: string) { 349 interface Params { 350 position: number; 351 transition: string; 352 leftBlank: number[]; 353 } 354 355 const params: Params = { 356 position: targetIndex, 357 transition: name, 358 leftBlank: this.leftBlank, 359 }; 360 this.browserController.showSelectBrowser(geometryTapIndex, geometryTransitionString, TAG, params); 361 } 362 363 onPageShow() { 364 } 365 366 aboutToDisappear(): void { 367 Log.debug(TAG, 'aboutToDisappear'); 368 ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack); 369 this.dataSource?.unregisterCallback('updateGroupData'); 370 this.dataSource?.unregisterCallback('updateCount'); 371 if (this.appBroadCast != null) { 372 this.appBroadCast.off(BroadCastConstants.BACK_PRESS_EVENT, this.backPressEventFunc); 373 this.appBroadCast.off(BroadCastConstants.ON_TAB_CHANGED, this.onTableChangedFunc); 374 this.appBroadCast.off(BroadCastConstants.RESET_STATE_EVENT, this.resetStateEventFunc); 375 this.appBroadCast.off(BroadCastConstants.UPDATE_DATA_SOURCE, this.updateDataSourceFunc); 376 this.appBroadCast.off(BroadCastConstants.RESET_ZERO, this.resetZeroFunc); 377 } 378 if (this.broadCast != null) { 379 this.broadCast.off(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc); 380 this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc); 381 this.broadCast.off(BroadCastConstants.GROUP_SELECT, this.groupSelectFunc); 382 this.broadCast.off(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc); 383 this.broadCast.off(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 384 this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadedFunc); 385 this.broadCast.off(BroadCastConstants.INIT_DATE_TEXT, this.initDateTextFunc); 386 } 387 } 388 389 updateGroupSelectMode(): void { 390 let groups: TimelineData[] = this.dataSource == null ? [] : this.dataSource.groups; 391 if (this.groupSelectMode.length == 0) { 392 Log.info(TAG, 'first updateGroupSelectMode'); 393 for (let i = 0; i < groups.length; i++) { 394 this.groupSelectMode.push(this.mSelectManager.isGroupSelected(i)); 395 } 396 } else { 397 Log.info(TAG, 'no first updateGroupSelectMode'); 398 for (let i = 0; i < groups.length; i++) { 399 Log.info(TAG, 'update one'); 400 this.groupSelectMode[i] = this.mSelectManager.isGroupSelected(i); 401 } 402 } 403 } 404 405 updateRightClickMenuList() { 406 this.rightClickMenuList = this.isSelectedMode 407 ? [Action.DELETE, Action.ADD, Action.INFO] 408 : [Action.MULTISELECT, Action.DELETE, Action.ADD, Action.INFO]; 409 } 410 411 updateYearMap(): void { 412 Log.info(TAG, 'updateYearMap'); 413 let groups: TimelineData[] = this.dataSource == null ? [] : this.dataSource.groups; 414 if (groups.length == 0) { 415 Log.error(TAG, 'year length is 0'); 416 return; 417 } 418 this.yearData = []; 419 420 let startGroup: TimelineData = groups[0]; 421 let count: number = startGroup.count as number; 422 let startTime: number = startGroup.startDate as number; 423 let endTime: number = startGroup.startDate as number; 424 425 for (let i = 1; i < groups.length; i++) { 426 let dateTaken: number = groups[i].startDate as number; 427 if (DateUtil.isTheSameYear(startTime, dateTaken)) { 428 count = count + groups[i].count as number; 429 endTime = dateTaken; 430 } else { 431 let groupData = new TimelineData(startTime, endTime, count); 432 this.yearData.push(groupData); 433 count = groups[i].count as number; 434 startTime = dateTaken; 435 endTime = dateTaken; 436 } 437 } 438 let groupData = new TimelineData(startTime, endTime, count); 439 this.yearData.push(groupData); 440 Log.info(TAG, 'updateYearMap end'); 441 } 442 443 onIndexChange(): void { 444 Log.info(TAG, `onIndexChange ${this.timelinePageIndex}`); 445 if (this.timelinePageIndex != Constants.INVALID && this.dataSource != null) { 446 this.scroller.scrollToIndex(this.dataSource.getPositionByIndex(this.timelinePageIndex)); 447 } 448 } 449 450 onIndexBackPress(callback: Function): void { 451 if (this.isInCurrentTab) { 452 callback(this.onModeChange()); 453 } 454 } 455 456 onTabChanged(index: number): void { 457 if (index == Constants.TIMELINE_PAGE_INDEX) { 458 this.isInCurrentTab = true; 459 this.onActive(); 460 } else { 461 this.isInCurrentTab = false; 462 this.onModeChange(); 463 this.onInActive(); 464 } 465 } 466 467 onStateReset(index: number): void { 468 if (index == Constants.TIMELINE_PAGE_INDEX) { 469 this.onModeChange(); 470 } 471 } 472 473 resetZero(pageNumber: number): void { 474 if (pageNumber == Constants.TIMELINE_PAGE_INDEX) { 475 this.scroller.scrollEdge(Edge.Top); 476 } 477 } 478 479 onMenuClicked(action: Action): void { 480 Log.info(TAG, `onMenuClicked, actionID: ${action.actionID}`); 481 let menuContext: MenuContext; 482 let menuOperation: MenuOperation; 483 if (action === Action.CANCEL) { 484 this.onModeChange(); 485 } else if (action === Action.MULTISELECT) { 486 this.isSelectedMode = true; 487 } else if (action === Action.SELECT_ALL) { 488 this.mSelectManager.selectAll(true); 489 } else if (action === Action.DESELECT_ALL) { 490 this.mSelectManager.deSelectAll(); 491 } else if (action === Action.DELETE) { 492 menuContext = new MenuContext(); 493 menuContext 494 .withSelectManager(this.mSelectManager) 495 .withFromSelectMode(this.isSelectedMode) 496 .withOperationStartCallback((): void => this.onDeleteStart()) 497 .withOperationEndCallback((): void => this.onDeleteEnd()) 498 .withBroadCast(this.broadCast); 499 menuOperation = MenuOperationFactory.getInstance() 500 .createMenuOperation(BatchDeleteMenuOperation, menuContext); 501 menuOperation.doAction(); 502 } else if (action === Action.SHARE) { 503 menuContext = new MenuContext(); 504 menuContext.withFromSelectMode(true).withSelectManager(this.mSelectManager); 505 menuOperation = MenuOperationFactory.getInstance() 506 .createMenuOperation(ShareMenuOperation, menuContext); 507 menuOperation.doAction(); 508 } else if (action === Action.INFO) { 509 this.hidePopup = true; 510 this.openDetailsDialog(); 511 } else if (action === Action.ADD) { 512 this.mSelectManager.getSelectedItems((selectedItems: Array<MediaItem>) => { 513 Log.info(TAG, `Get selected items success, size: ${selectedItems.length}`); 514 this.routeToSelectAlbumPage(MediaOperationType.Add, selectedItems); 515 }) 516 } 517 } 518 519 routeToSelectAlbumPage(pageType: string, selectedItems: Array<MediaItem>): void { 520 Log.info(TAG, 'Route to select album page'); 521 router.pushUrl({ 522 url: 'pages/MediaOperationPage', 523 params: { 524 pageFrom: Constants.MEDIA_OPERATION_FROM_TIMELINE, 525 pageType: pageType, 526 selectedItems: selectedItems 527 } 528 }); 529 this.routerStart = true; 530 } 531 532 async openDetailsDialog(): Promise<void> { 533 if (this.totalSelectedCount == 0) { 534 Log.error(TAG, 'no select error'); 535 return; 536 } else if (this.totalSelectedCount == 1) { 537 Log.info(TAG, 'totalSelectedCount is 1'); 538 await this.mSelectManager.getSelectedItems((selectItems: MediaItem[]) => { 539 Log.info(TAG, `openDetailsDialog selectItems.length: ${selectItems.length}`); 540 if (selectItems.length != 1) { 541 Log.error(TAG, 'get selectItems is error'); 542 return; 543 } 544 this.broadCast.emit(BroadCastConstants.SHOW_DETAIL_DIALOG, [selectItems[0], false]); 545 }); 546 } else { 547 await this.mSelectManager.getSelectedItems((selectItems: MediaItem[]) => { 548 Log.info(TAG, `openDetailsDialog selectItems.length: ${selectItems.length}`); 549 if (selectItems.length <= 1) { 550 Log.error(TAG, 'get selectItems is error'); 551 return; 552 } 553 let size = 0; 554 selectItems.forEach((item) => { 555 Log.info(TAG, `openDetailsDialog item.size: ${item.size}`); 556 size = size + item.size; 557 }) 558 559 Log.info(TAG, `openDetailsDialog size: ${size}`); 560 this.broadCast.emit(BroadCastConstants.SHOW_MULTI_SELECT_DIALOG, [this.totalSelectedCount, size]); 561 }); 562 return; 563 } 564 } 565 566 onDeleteStart(): void { 567 Log.info(TAG, `onDeleteStart`); 568 this.deleteMode = true; 569 this.isDataFreeze = true; 570 if (this.dataSource != null) { 571 this.dataSource.unregisterTimelineObserver(); 572 this.dataSource.freeze(); 573 } 574 } 575 576 onDeleteEnd(): void { 577 Log.info(TAG, `onDeleteEnd`); 578 this.isDataFreeze = false; 579 this.onModeChange(); 580 if (this.dataSource != null) { 581 this.dataSource.registerTimelineObserver(); 582 this.dataSource.onChange('image'); 583 this.dataSource.unfreeze(); 584 } 585 } 586 587 onCopyStart(): void { 588 Log.info(TAG, `onCopyStart`); 589 this.isDataFreeze = true; 590 if (this.dataSource != null) { 591 this.dataSource.unregisterTimelineObserver(); 592 this.dataSource.freeze(); 593 } 594 595 } 596 597 onCopyEnd(err: Object, count: number, total: number): void { 598 Log.info(TAG, `onCopyEnd count: ${count}, total: ${total}`); 599 this.isDataFreeze = false; 600 this.onModeChange(); 601 if (this.dataSource != null) { 602 this.dataSource.registerTimelineObserver(); 603 this.dataSource.onChange('image'); 604 this.dataSource.unfreeze(); 605 } 606 if (err) { 607 UiUtil.showToast($r('app.string.copy_failed_single')); 608 } 609 } 610 611 onMoveStart(): void { 612 Log.info(TAG, `onMoveStart`); 613 this.isDataFreeze = true; 614 if (this.dataSource != null) { 615 this.dataSource.unregisterTimelineObserver(); 616 this.dataSource.freeze(); 617 } 618 } 619 620 onMoveEnd(err: Object, count: number, total: number): void { 621 Log.info(TAG, `onMoveEnd count: ${count}, total: ${total}`); 622 this.isDataFreeze = false; 623 this.onModeChange(); 624 if (this.dataSource != null) { 625 this.dataSource.registerTimelineObserver(); 626 this.dataSource.unfreeze(); 627 this.dataSource.switchRefreshOn(); 628 this.dataSource.onChange('image'); 629 } 630 if (err) { 631 UiUtil.showToast($r('app.string.move_failed_single')); 632 } 633 } 634 635 onModeChange() { 636 Log.debug(TAG, `onModeChange current mode ${this.isSelectedMode}`); 637 if (this.isSelectedMode) { 638 this.isSelectedMode = false; 639 this.isAllSelected = false; 640 this.mSelectManager.onModeChange(false); 641 this.updateGroupSelectMode(); 642 AppStorage.delete(Constants.PHOTO_GRID_SELECT_MANAGER); 643 return true; 644 } 645 return false; 646 } 647 648 // The callbacks after index page shows 649 onIndexPageShow() { 650 Log.info(TAG, `[onIndexPageShow] isShow=${this.isShow}, isInCurrentTab=${this.isInCurrentTab}`); 651 if (this.isShow && this.isInCurrentTab) { 652 let params: Params = router.getParams() as Params; 653 if (this.routerStart && params != null && params.pageType != null) { 654 Log.info(TAG, `MediaOperation back ${JSON.stringify(params)}`) 655 if (params.pageType === MediaOperationType.Add) { 656 this.addOperation(params.albumName, params.albumUri); 657 } 658 } 659 this.routerStart = false; 660 this.onActive(); 661 } else if (!this.isShow && this.isInCurrentTab) { 662 this.onInActive(); 663 } else { 664 } 665 } 666 667 // The callback when current page is in the foreground 668 onActive() { 669 if (!this.isActive) { 670 Log.info(TAG, 'onActive'); 671 this.isActive = true; 672 673 this.dataSource?.onActive(); 674 if (this.isSelectedMode) { 675 this.totalSelectedCount = this.mSelectManager.getSelectedCount(); 676 this.dataSource?.forceUpdate(); 677 } 678 } 679 } 680 681 // The callback when current page is in the background 682 onInActive() { 683 if (this.isActive) { 684 Log.info(TAG, 'onInActive'); 685 this.isActive = false; 686 this.dataSource?.onInActive(); 687 } 688 } 689 690 getGeometryTransitionId(item: ViewData, index: number): string { 691 let mediaItem = item.mediaItem as MediaItem; 692 if (mediaItem) { 693 return TAG + mediaItem.getHashCode() + this.mSelectManager.isItemSelected(mediaItem.uri, item.viewIndex); 694 } else { 695 return TAG + item.viewIndex; 696 } 697 } 698 699 build() { 700 Stack() { 701 Column() { 702 if (this.isEmpty) { 703 NoPhotoIndexComponent({ index: Constants.TIMELINE_PAGE_INDEX, hasBarSpace: true }) 704 } else { 705 TimelinePageActionBar({ 706 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 707 totalSelectedCount: $totalSelectedCount 708 }); 709 710 Stack() { 711 Grid(this.scroller, this.layoutOptions) { 712 LazyForEach(this.dataSource as TimelineDataSource, (item: ViewData, index?: number) => { 713 if (!!item) { 714 if (item.viewType == ViewType.GROUP_TITLE) { 715 GridItem() { 716 TimelineTitleComponent({ 717 groupData: item.viewData, 718 mPosition: item.viewIndex, 719 isSelected: this.groupSelectMode[item.viewIndex] 720 }) 721 } 722 .key('TimelinePage_GridItem' + index) 723 } else if (item.viewType == ViewType.ITEM) { 724 GridItem() { 725 ImageGridItemComponent({ 726 dataSource: this.dataSource, 727 item: item.mediaItem, 728 mPosition: item.viewIndex, 729 isSelected: this.isSelectedMode ? 730 this.mSelectManager.isItemSelected((item.mediaItem as MediaItem).uri as string, 731 item.viewIndex) : false, 732 pageName: Constants.PHOTO_TRANSITION_TIMELINE, 733 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 734 onMenuClickedForSingleItem: (action: Action, currentPhoto: MediaItem): void => 735 this.onMenuClickedForSingleItem(action, currentPhoto), 736 geometryTransitionString: this.getGeometryTransitionId(item, index as number), 737 selectedCount: $totalSelectedCount 738 }) 739 } 740 .aspectRatio(1) 741 .key('TimelinePage_GridItem' + index) 742 .zIndex(index === this.placeholderIndex ? 1 : 0) 743 } 744 } 745 }, (item: ViewData, index?: number) => { 746 if (item == null || item == undefined) { 747 return (JSON.stringify(item) + index) as string; 748 } 749 if (item.viewType == ViewType.GROUP_TITLE) { 750 return (JSON.stringify(item.viewData) + this.groupSelectMode[item.viewIndex]) as string; 751 } else { 752 return this.getGeometryTransitionId(item, index as number) as string; 753 } 754 }) 755 } 756 .edgeEffect(EdgeEffect.Spring) 757 .columnsTemplate('1fr '.repeat(this.gridRowCount)) 758 .scrollBar(BarState.Off) 759 .columnsGap(Constants.GRID_GUTTER) 760 .rowsGap(Constants.GRID_GUTTER) 761 .cachedCount(Constants.GRID_CACHE_ROW_COUNT) 762 .onScrollIndex((first) => { 763 let scrollMediaItem: MediaItem | null = this.dataSource == null ? 764 null : this.dataSource.getMediaItemByPosition(first) as MediaItem; 765 if (scrollMediaItem?.getDataTaken()) { 766 this.dateText = DateUtil.getLocalizedYearAndMonth(scrollMediaItem.getDataTaken()); 767 Log.debug(TAG, `scrollIndex=${first}, dateTaken=${scrollMediaItem.getDataTaken()}`); 768 } else { 769 Log.warn(TAG, `scrollIndex ${first} out of active window`); 770 } 771 }) 772 773 if (this.isShowScrollBar) { 774 TimelineScrollBar({ scroller: this.scroller }) 775 } 776 } 777 .layoutWeight(1) 778 } 779 } 780 .alignItems(HorizontalAlign.Start) 781 .justifyContent(FlexAlign.Start) 782 .margin({ 783 bottom: this.isHorizontal ? 0 : $r('app.float.tab_bar_vertical_height') 784 }) 785 786 if (this.isSelectedMode) { 787 TimelinePageToolBar({ 788 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 789 totalSelectedCount: $totalSelectedCount 790 }) 791 } 792 CustomDialogView({ broadCast: $broadCast }) 793 } 794 } 795 796 private onMenuClickedForSingleItem(action: Action, currentPhoto: MediaItem) { 797 Log.info(TAG, `single menu click, action: ${action?.actionID}, currentUri: ${currentPhoto?.uri}`); 798 if (currentPhoto == undefined) { 799 return; 800 } 801 let menuOperation: MenuOperation; 802 let menuContext: MenuContext; 803 if (action === Action.DELETE) { 804 menuContext = new MenuContext(); 805 menuContext.withMediaItem(currentPhoto).withBroadCast(this.broadCast); 806 menuOperation = MenuOperationFactory.getInstance() 807 .createMenuOperation(DeleteMenuOperation, menuContext); 808 menuOperation.doAction(); 809 } else if (action === Action.ADD) { 810 this.isMvOrCpSeparatesItem = true; 811 this.mvOrCpSeparatesItem = currentPhoto; 812 this.routeToSelectAlbumPage(MediaOperationType.Add, [currentPhoto]); 813 } else if (action === Action.INFO) { 814 this.broadCast.emit(BroadCastConstants.SHOW_DETAIL_DIALOG, [currentPhoto, false]); 815 } 816 } 817 818 // Calculate the number of squares per row 819 private initGridRowCount(): void { 820 let sideBarWidth = this.isSidebar ? Constants.TAB_BAR_WIDTH : 0; 821 let contentWidth = ScreenManager.getInstance().getWinWidth() - sideBarWidth; 822 let margin = 0; 823 let maxThumbWidth = px2vp(Constants.GRID_IMAGE_SIZE) * Constants.GRID_MAX_SIZE_RATIO; 824 let newCount = Math.max(Constants.GRID_MIN_COUNT, 825 Math.round(((contentWidth - Constants.NUMBER_2 * margin) + 826 Constants.GRID_GUTTER) / (maxThumbWidth + Constants.GRID_GUTTER))); 827 if (newCount != this.gridRowCount) { 828 this.gridRowCount = newCount; 829 } 830 Log.info(TAG, `initGridRowCount contentWidth: ${contentWidth}`); 831 } 832 833 private async addOperation(albumName: string, albumUri: string) { 834 let menuContext = new MenuContext(); 835 let onCopyStartFunc = (): void => this.onCopyStart(); 836 if (this.isMvOrCpSeparatesItem) { 837 menuContext.withMediaItem(this.mvOrCpSeparatesItem as MediaItem); 838 this.onCopyStart && this.onCopyStart(); 839 this.isMvOrCpSeparatesItem = false; 840 this.mvOrCpSeparatesItem = undefined; 841 } else { 842 menuContext.withSelectManager(this.mSelectManager).withOperationStartCallback(onCopyStartFunc); 843 } 844 menuContext.withOperationEndCallback((err: Object, count: number, total: number): void => 845 this.onCopyEnd(err as Object, count, total)) 846 .withBroadCast(this.broadCast) 847 .withTargetAlbumName(albumName).withAlbumUri(albumUri); 848 let menuOperation = MenuOperationFactory.getInstance().createMenuOperation(AddMenuOperation, menuContext); 849 menuOperation.doAction(); 850 } 851 852 private onUpdateFavorState(item: MediaItem): void { 853 Log.debug(TAG, 'onUpdateFavorState favor'); 854 if (this.dataSource != null) { 855 let index = this.dataSource.getIndexByMediaItem(item); 856 if (index == Constants.NOT_FOUND) { 857 return; 858 } 859 let flushIndex = this.dataSource.getPositionByIndex(index); 860 Log.debug(TAG, `onUpdateFavorState favor flushIndex ${flushIndex}`); 861 this.dataSource.onDataChanged(flushIndex); 862 } 863 } 864} 865