1/**
2 * Copyright (c) 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 prompt from '@system.prompt';
18import Curves from '@ohos.curves';
19import { MenuOperation } from '@ohos/common';
20import {
21  Action,
22  AddMenuOperation,
23  AlbumDefine,
24  AlbumInfo,
25  BatchDeleteMenuOperation,
26  BatchRemoveMenuOperation,
27  BroadCast,
28  BroadCastConstants,
29  BroadCastManager,
30  BrowserConstants,
31  CommonObserverCallback,
32  Constants,
33  DeleteMenuOperation,
34  ImageUtil,
35  JumpSourceToMain,
36  Log,
37  MediaDataSource,
38  MediaItem,
39  MediaObserver,
40  MediaOperationType,
41  MenuContext,
42  MenuOperationFactory,
43  MoveMenuOperation,
44  RemoveMenuOperation,
45  ScreenManager,
46  SelectManager,
47  ShareMenuOperation,
48  TraceControllerUtils,
49  UiUtil,
50  UserFileManagerAccess,
51  ViewData
52} from '@ohos/common';
53import {
54  BrowserController,
55  CustomDialogView,
56  GridScrollBar,
57  ImageGridItemComponent,
58  NoPhotoComponent
59} from '@ohos/common/CommonComponents';
60import { RecoverMenuOperation } from '@ohos/browser';
61import {
62  BatchRecoverMenuOperation,
63  ClearRecycleMenuOperation,
64  PhotoGridPageActionBar,
65  PhotoGridPageToolBar
66} from '@ohos/browser/BrowserComponents';
67
68
69const TAG: string = 'PhotoGridView';
70AppStorage.setOrCreate('photoGridPageIndex', Constants.INVALID);
71
72// Album View
73@Component
74export struct PhotoGridView {
75  @State isShowScrollBar: boolean = false;
76  @Provide isEmpty: boolean = false;
77  @State gridRowCount: number = 0;
78  @Consume @Watch('updateRightClickMenuList') isSelectedMode: boolean;
79  @Provide isAllSelected: boolean = false;
80  @State totalSelectedCount: number = 0;
81  @StorageLink('isHorizontal') isHorizontal: boolean = ScreenManager.getInstance().isHorizontal();
82  @Provide broadCast: BroadCast = new BroadCast();
83  @Consume isShow: boolean;
84  @Provide isShowBar: boolean = true;
85  @Provide moreMenuList: Action[] = [];
86  @Provide rightClickMenuList: Action[] = [];
87  @StorageLink('photoGridPageIndex') @Watch('onIndexChange') photoGridPageIndex: number = Constants.INVALID;
88  @StorageLink('isSplitMode') isSplitMode: boolean = ScreenManager.getInstance().isSplitMode();
89  @StorageLink('leftBlank') leftBlank: number[] =
90    [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()];
91  @Prop @Watch('onPageChanged') pageStatus: boolean = false;
92  @State gridItemWidth: number = 0;
93  @StorageLink('photoGridActionBarOpacity') photoGridActionBarOpacity: number = 0;
94  @StorageLink('photoGridViewOpacity') photoGridViewOpacity: number = 0;
95  albumInfo: AlbumInfo = new AlbumInfo();
96  title: string = '';
97  deviceName: string = '';
98  dataSource: MediaDataSource = new MediaDataSource(Constants.DEFAULT_SLIDING_WIN_SIZE);
99  scroller: Scroller = new Scroller();
100  isDataFreeze = false;
101  mSelectManager = new SelectManager();
102  isActive = false;
103  isDistributedAlbum = false;
104  deleteMode: boolean = false;
105  routerStart = false;
106  isFromFACard = false;
107  @StorageLink('placeholderIndex') @Watch('onPlaceholderChanged') placeholderIndex: number = -1;
108  @Provide hidePopup: boolean = false;
109  @ObjectLink browserController: BrowserController;
110  private dataObserver: CommonObserverCallback = new CommonObserverCallback(this);
111  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
112  // 选择模式下,鼠标对着未勾选项按右键弹框时,移动和复制菜单点击事件的标识位
113  private isMvOrCpSeparatesItem: boolean = false;
114  private mvOrCpSeparatesItem: MediaItem = new MediaItem();
115  private photoTotalCount: number = 0;
116  private params: Params | null = null;
117  private scrollIndex: number = 0;
118  private onWindowSizeChangeCallBack: Function = () => {
119    // 后续phone缩略图支持横竖屏后再放开
120  }
121  private selectFunc: Function = (position: number, key: string, value: boolean, callback: Function): void =>
122  this.select(position, key, value, callback);
123  private jumpPhotoBrowserFunc: Function = (name: string, item: MediaItem,
124                                            geometryTapIndex: number, geometryTransitionString: string): void =>
125  this.jumpPhotoBrowser(name, item, geometryTapIndex, geometryTransitionString);
126  private jumpThirdPhotoBrowserFunc: Function = (name: string, item: MediaItem,
127                                                 geometryTapIndex: number, geometryTransitionString: string): void =>
128  this.jumpThirdPhotoBrowser(name, item, geometryTapIndex, geometryTransitionString);
129  private onLoadingFinishedFunc: Function = (size: number): void => this.onLoadingFinished(size);
130  private onDataReloadedFunc: Function = (): void => this.onDataReloaded();
131  private onUpdateFavorStateFunc: Function = (item: MediaItem): void => this.onUpdateFavorState(item);
132  private onDoAnimationFunc: Function = (): void => this.onDoAnimation();
133
134  onPlaceholderChanged() {
135    Log.debug(TAG, 'onPlaceholderChanged placeholderIndex is ' + this.placeholderIndex);
136    if (this.placeholderIndex != -1) {
137      this.scroller.scrollToIndex(this.placeholderIndex);
138    }
139  }
140
141  initParams(): void {
142    this.isSelectedMode = false;
143    this.isShow = true;
144  }
145
146  onIndexChange(): void {
147    Log.info(TAG, `onIndexChange ${this.photoGridPageIndex}`)
148    if (this.photoGridPageIndex != Constants.INVALID) {
149      this.scroller.scrollToIndex(this.photoGridPageIndex);
150    }
151  }
152
153  doAnimation(): void {
154    animateTo({
155      duration: BrowserConstants.PHONE_LINK_ALBUM_ACTIONBAR_DURATION,
156      delay: BrowserConstants.PHONE_LINK_ALBUM_ACTIONBAR_DELAY,
157      curve: Curve.Sharp
158    }, () => {
159      AppStorage.setOrCreate<number>(Constants.KEY_OF_ALBUM_ACTIONBAR_OPACITY, 1);
160    })
161    animateTo({
162      duration: BrowserConstants.PHONE_LINK_OUT_PHOTO_GRID_ACTIONBAR_DURATION,
163      curve: Curve.Sharp
164    }, () => {
165      AppStorage.setOrCreate<number>(Constants.KEY_OF_PHOTO_GRID_ACTIONBAR_OPACITY, 0);
166      AppStorage.setOrCreate<number>(Constants.KEY_OF_PHOTO_GRID_VIEW_OPACITY, 0);
167      AppStorage.setOrCreate<number>(Constants.KEY_OF_ALBUM_OPACITY, 1);
168    })
169    animateTo({
170      duration: BrowserConstants.PHONE_LINK_PHOTO_GRID_TO_ALBUM_DURATION,
171      curve: Curve.Friction
172    }, () => {
173      AppStorage.setOrCreate<number>(Constants.KEY_OF_SELECTED_ALBUM_INDEX, -1);
174      AppStorage.setOrCreate<boolean>(Constants.KEY_OF_IS_SHOW_PHOTO_GRID_VIEW, false);
175      AppStorage.setOrCreate<string>(Constants.KEY_OF_SELECTED_ALBUM_URI, '');
176    })
177    animateTo({
178      duration: BrowserConstants.PHONE_LINK_PHOTO_GRID_TO_ALBUM_SCALE_DURATION,
179      curve: Curve.Friction
180    }, () => {
181      AppStorage.setOrCreate<number>(Constants.KEY_OF_ALBUM_OTHER_SCALE, 1);
182    })
183  }
184
185  onMenuClicked(action: Action): void {
186    Log.info(TAG, `onMenuClicked, action: ${action.actionID}`);
187    let menuContext: MenuContext;
188    let menuOperation: MenuOperation;
189    if (action.actionID === Action.BACK.actionID) {
190      if (this.isFromFACard) {
191        router.replaceUrl({
192          url: 'pages/index',
193          params: {
194            jumpSource: JumpSourceToMain.ALBUM,
195          }
196        });
197      } else {
198        if (router.getState().name === Constants.USER_FILE_MANAGER_PHOTO_TRANSITION_ALBUM) {
199          router.back();
200        } else {
201          this.doAnimation();
202        }
203      }
204    } else if (action.actionID === Action.CANCEL.actionID) {
205      this.onModeChange();
206    } else if (action.actionID === Action.MULTISELECT.actionID) {
207      this.isSelectedMode = true;
208    } else if (action.actionID === Action.SELECT_ALL.actionID) {
209      this.mSelectManager.selectAll(true);
210    } else if (action.actionID === Action.DESELECT_ALL.actionID) {
211      this.mSelectManager.deSelectAll();
212    } else if (action.actionID === Action.DELETE.actionID) {
213      menuContext = new MenuContext();
214      menuContext
215        .withSelectManager(this.mSelectManager)
216        .withOperationStartCallback((): void => this.onDeleteStart())
217        .withOperationEndCallback((): void => this.onDeleteEnd())
218        .withBroadCast(this.broadCast)
219        .withAlbumUri(this.albumInfo.uri)
220        .withFromSelectMode(this.isSelectedMode)
221        .withAlbumInfo(this.albumInfo)
222      menuOperation = MenuOperationFactory.getInstance()
223        .createMenuOperation(BatchDeleteMenuOperation, menuContext);
224      menuOperation.doAction();
225    } else if (action.actionID === Action.SHARE.actionID) {
226      menuContext = new MenuContext();
227      menuContext.withFromSelectMode(true).withSelectManager(this.mSelectManager);
228      menuOperation = MenuOperationFactory.getInstance()
229        .createMenuOperation(ShareMenuOperation, menuContext);
230      menuOperation.doAction();
231    } else if (action.actionID === Action.INFO.actionID) {
232      this.hidePopup = true;
233      this.openDetailsDialog();
234    } else if (action.actionID === Action.CLEAR_RECYCLE.actionID) {
235      menuContext = new MenuContext();
236      menuContext
237        .withSelectManager(this.mSelectManager)
238        .withOperationStartCallback((): void => this.onDeleteStart())
239        .withOperationEndCallback((): void => this.onDeleteEnd())
240        .withBroadCast(this.broadCast)
241        .withAlbumUri(UserFileManagerAccess.getInstance()
242          .getSystemAlbumUri(UserFileManagerAccess.TRASH_ALBUM_SUB_TYPE))
243        .withAlbumInfo(this.albumInfo)
244      menuOperation = MenuOperationFactory.getInstance()
245        .createMenuOperation(ClearRecycleMenuOperation, menuContext);
246      menuOperation.doAction();
247    } else if (action.actionID === Action.RECOVER.actionID) {
248      menuContext = new MenuContext();
249      menuContext
250        .withAlbumUri(UserFileManagerAccess.getInstance()
251          .getSystemAlbumUri(UserFileManagerAccess.TRASH_ALBUM_SUB_TYPE))
252        .withSelectManager(this.mSelectManager)
253        .withOperationStartCallback((): void => this.onDeleteStart())
254        .withOperationEndCallback((): void => this.onDeleteEnd())
255        .withBroadCast(this.broadCast)
256        .withAlbumInfo(this.albumInfo)
257      menuOperation = MenuOperationFactory.getInstance()
258        .createMenuOperation(BatchRecoverMenuOperation, menuContext);
259      menuOperation.doAction();
260    } else if (action.actionID === Action.MOVE.actionID) {
261      this.mSelectManager.getSelectedItems((selectedItems: Array<MediaItem>) => {
262        Log.info(TAG, `Get selected items success, size: ${selectedItems.length}, start route to select album page`);
263        this.routeToSelectAlbumPage(MediaOperationType.Move, selectedItems);
264      })
265    } else if (action.actionID === Action.ADD.actionID) {
266      this.mSelectManager.getSelectedItems((selectedItems: Array<MediaItem>) => {
267        Log.info(TAG, `Get selected items success, size: ${selectedItems.length}, start route to select album page`);
268        this.routeToSelectAlbumPage(MediaOperationType.Add, selectedItems);
269      })
270    } else if (action.actionID === Action.REMOVE_FROM.actionID) {
271      menuContext = new MenuContext();
272      menuContext
273        .withSelectManager(this.mSelectManager)
274        .withOperationStartCallback((): void => this.onRemoveStart())
275        .withOperationEndCallback((): void => this.onRemoveEnd())
276        .withBroadCast(this.broadCast)
277        .withAlbumUri(this.albumInfo.uri)
278        .withFromSelectMode(this.isSelectedMode)
279      menuOperation = MenuOperationFactory.getInstance()
280        .createMenuOperation(BatchRemoveMenuOperation, menuContext);
281      menuOperation.doAction();
282    } else if (action.actionID === Action.NEW.actionID) {
283      this.routeToAddMediaPage();
284    } else if (action.actionID === Action.DOWNLOAD.actionID) {
285      menuContext = new MenuContext();
286      menuContext
287        .withSelectManager(this.mSelectManager)
288        .withOperationStartCallback((): void => this.onDownloadStart())
289        .withOperationEndCallback(async (err: Error, count: number, total: number): Promise<void> =>
290        this.onDownloadEnd(err as Object, count, total))
291        .withBroadCast(this.broadCast)
292      menuOperation = MenuOperationFactory.getInstance().createMenuOperation(AddMenuOperation, menuContext);
293      menuOperation.doAction();
294    }
295  }
296
297  async openDetailsDialog(): Promise<void> {
298    if (this.totalSelectedCount == 0) {
299      Log.error(TAG, 'no select error');
300      return;
301    } else if (this.totalSelectedCount == 1) {
302      Log.info(TAG, 'totalSelectedCount is 1');
303      await this.mSelectManager.getSelectedItems((selectItems: MediaItem[]) => {
304        if (selectItems.length != 1) {
305          Log.error(TAG, 'get selectItems is error');
306          return;
307        }
308        this.broadCast.emit(BroadCastConstants.SHOW_DETAIL_DIALOG, [selectItems[0], this.isDistributedAlbum]);
309      });
310    } else {
311      await this.mSelectManager.getSelectedItems((selectItems: MediaItem[]) => {
312        if (selectItems.length <= 1) {
313          Log.error(TAG, 'get selectItems is error');
314          return;
315        }
316        let size = 0;
317        selectItems.forEach((item) => {
318          size = size + item.size;
319        })
320
321        Log.info(TAG, `openDetailsDialog size: ${size}`);
322        this.broadCast.emit(BroadCastConstants.SHOW_MULTI_SELECT_DIALOG, [this.totalSelectedCount, size]);
323      });
324      return;
325    }
326  }
327
328  routeToSelectAlbumPage(pageType: string, selectedItems: Array<MediaItem>): void {
329    this.routerStart = true;
330    router.pushUrl({
331      url: 'pages/MediaOperationPage',
332      params: {
333        pageFrom: Constants.MEDIA_OPERATION_FROM_PHOTO_GRID,
334        pageType: pageType,
335        albumInfo: this.albumInfo,
336        selectedItems: selectedItems
337      }
338    });
339  }
340
341  routeToAddMediaPage(): void {
342    router.pushUrl({
343      url: 'pages/AlbumSelect',
344      params: {
345        albumName: this.albumInfo.albumName,
346        albumUri: this.albumInfo.uri
347      }
348    });
349  }
350
351  onCopyStart(): void {
352    Log.info(TAG, `onCopyStart`);
353    this.isDataFreeze = true;
354    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
355    this.dataSource.freeze();
356  }
357
358  onCopyEnd(err: Object, count: number, total: number): void {
359    Log.info(TAG, `onCopyEnd count: ${count}, total: ${total}`);
360    this.isDataFreeze = false;
361    this.onModeChange();
362    MediaObserver.getInstance().registerObserver(this.dataObserver);
363    this.dataSource.onChange('image');
364    this.dataSource.unfreeze();
365    if (err) {
366      UiUtil.showToast($r('app.string.copy_failed_single'));
367    }
368  }
369
370  onDownloadStart(): void {
371    Log.info(TAG, `onDownloadStart`);
372    this.isDataFreeze = true;
373    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
374    this.dataSource.freeze();
375  }
376
377  async onDownloadEnd(err: Object, count: number, total: number): Promise<void> {
378    Log.info(TAG, `onDownloadEnd count: ${count}, total: ${total}`);
379    this.isDataFreeze = false;
380    this.onModeChange();
381    MediaObserver.getInstance().registerObserver(this.dataObserver);
382    this.dataSource.onChange('image');
383    this.dataSource.unfreeze();
384    if (err) {
385      if (total > 1) {
386        Log.error(TAG, `get selectItems is error ${count}`);
387        let str = await UiUtil.getResourceString($r('app.string.download_failed_multi'));
388        let message = str.replace('%d', count.toString());
389        prompt.showToast({
390          message: message,
391          duration: UiUtil.TOAST_DURATION,
392          bottom: '200vp'
393        });
394      } else {
395        UiUtil.showToast($r('app.string.download_failed_single'))
396      }
397    } else {
398      UiUtil.showToast($r('app.string.download_progress_done'));
399    }
400  }
401
402  onMoveStart(): void {
403    Log.info(TAG, `onMoveStart`);
404    this.isDataFreeze = true;
405    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
406    this.dataSource.freeze();
407  }
408
409  onMoveEnd(err: Object, count: number, total: number): void {
410    Log.info(TAG, `onMoveEnd count: ${count}, total: ${total}`);
411    this.isDataFreeze = false;
412    this.onModeChange();
413    MediaObserver.getInstance().registerObserver(this.dataObserver);
414    this.dataSource.switchRefreshOn();
415    this.dataSource.onChange('image');
416    this.dataSource.unfreeze();
417    if (err) {
418      UiUtil.showToast($r('app.string.move_failed_single'));
419    }
420  }
421
422  onDeleteStart(): void {
423    Log.info(TAG, `onDeleteStart`);
424    this.deleteMode = true;
425    this.isDataFreeze = true;
426    this.onModeChange();
427    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
428    this.dataSource.freeze();
429    MediaObserver.getInstance().freezeNotify();
430  }
431
432  onDeleteEnd(): void {
433    Log.info(TAG, `onDeleteEnd`);
434    this.isDataFreeze = false;
435    MediaObserver.getInstance().unfreezeNotify();
436    MediaObserver.getInstance().forceNotify();
437    MediaObserver.getInstance().registerObserver(this.dataObserver);
438    this.dataSource.onChange('image');
439    this.dataSource.unfreeze();
440  }
441
442  onRemoveStart(): void {
443    Log.info(TAG, `onRemoveStart`);
444    this.deleteMode = true;
445    this.isDataFreeze = true;
446    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
447    this.dataSource.freeze();
448  }
449
450  onRemoveEnd(): void {
451    Log.info(TAG, `onRemoveEnd`);
452    this.isDataFreeze = false;
453    this.onModeChange();
454    MediaObserver.getInstance().registerObserver(this.dataObserver);
455    this.dataSource.onChange('image');
456    this.dataSource.unfreeze();
457  }
458
459  onModeChange(): void {
460    Log.info(TAG, 'onModeChange');
461    this.isSelectedMode = false;
462    this.isAllSelected = false;
463    this.mSelectManager.onModeChange(false);
464    AppStorage.delete(Constants.PHOTO_GRID_SELECT_MANAGER);
465  }
466
467  onPageChanged(): void {
468    this.params = router.getParams() as Params;
469    if (this.pageStatus) {
470      this.onPageShow();
471    } else {
472      this.onPageHide();
473    }
474  }
475
476  onPageShow(): void {
477    this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []);
478    this.isShow = true;
479    if (this.routerStart && this.params != null && this.params.pageType != null) {
480      Log.info(TAG, 'MediaOperation back');
481      if (this.params.pageType === MediaOperationType.Move) {
482        this.moveOperation(this.params.albumName, this.params.albumUri);
483      } else if (this.params.pageType === MediaOperationType.Add) {
484        this.addOperation(this.params.albumName, this.params.albumUri);
485      }
486    }
487    MediaObserver.getInstance().registerObserver(this.dataObserver);
488    this.isMvOrCpSeparatesItem = false;
489    this.mvOrCpSeparatesItem = new MediaItem();
490    this.routerStart = false;
491    this.onActive();
492  }
493
494  onPageHide(): void {
495    this.isShow = false;
496    this.onInActive();
497  }
498
499  onActive(): void {
500    if (!this.isActive) {
501      Log.info(TAG, 'onActive');
502      this.isActive = true;
503      this.dataSource && this.dataSource.onActive();
504      if (this.isSelectedMode) {
505        this.totalSelectedCount = this.mSelectManager.getSelectedCount();
506        this.dataSource.forceUpdate();
507      }
508    }
509  }
510
511  onInActive(): void {
512    if (this.isActive) {
513      Log.info(TAG, 'onInActive');
514      this.isActive = false;
515    }
516  }
517
518  updateRightClickMenuList(): void {
519    if (!this.isSelectedMode) {
520      this.onModeChange();
521    }
522    this.rightClickMenuList = [];
523    if (this.albumInfo) {
524      let isRecycleAlbum: boolean = this.albumInfo.isTrashAlbum;
525      if (isRecycleAlbum) {
526        this.rightClickMenuList = [Action.RECOVER, Action.DELETE,
527          this.isSelectedMode ? Action.MULTISELECT_INVALID : Action.MULTISELECT];
528      } else {
529        if (!this.isSelectedMode) {
530          this.rightClickMenuList.push(Action.MULTISELECT)
531        }
532        this.rightClickMenuList.push(Action.DELETE);
533        if (!this.albumInfo.isSystemAlbum) {
534          this.rightClickMenuList.push(Action.MOVE);
535          this.rightClickMenuList.push(Action.REMOVE_FROM);
536        }
537        this.rightClickMenuList.push(Action.ADD, Action.INFO);
538      }
539    }
540  }
541
542  aboutToAppear(): void {
543    Log.debug(TAG, `aboutToAppear`);
544    TraceControllerUtils.startTrace('PhotoGridPageAboutToAppear');
545    this.initParams();
546    if (router.getState().name === Constants.USER_FILE_MANAGER_PHOTO_TRANSITION_ALBUM) {
547      this.photoGridActionBarOpacity = 1;
548      this.photoGridViewOpacity = 1;
549    }
550    let param: ParamAlbumInfo;
551    param = router.getParams() as ParamAlbumInfo;
552    if (!param || (param && !param.item)) {
553      param = AppStorage.get<ParamAlbumInfo>(Constants.KEY_OF_PHOTO_GRID_VIEW_ALBUM_ITEM) as ParamAlbumInfo;
554    }
555    if (param != null) {
556      if (param.isFromFACard) {
557        this.isFromFACard = param.isFromFACard;
558      }
559      this.albumInfo = JSON.parse(param.item);
560      this.title = this.albumInfo.albumName;
561      this.dataSource.setAlbumUri(this.albumInfo.uri);
562      if (this.albumInfo.mediaItem) {
563        let mediaItem = this.albumInfo.mediaItem;
564        this.dataSource.items = [mediaItem];
565        this.dataSource.size = 1;
566        this.dataSource.dataIndexes = [0];
567        this.photoTotalCount = this.albumInfo.count;
568      }
569    }
570
571    let self = this;
572    this.dataSource.setBroadCast(this.broadCast)
573    this.mSelectManager.setPhotoDataImpl();
574    this.mSelectManager.setAlbumUri(this.albumInfo.uri);
575    this.mSelectManager.setGetMediaItemFunc(
576      (uri: string, itemNotifyCallback: Function): MediaItem | undefined => {
577        return this.dataSource?.getMediaItemByUriFromAll(uri, itemNotifyCallback);
578      });
579    MediaObserver.getInstance().registerObserver(this.dataObserver);
580
581    this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc);
582    this.broadCast.on(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc);
583    this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc);
584    this.broadCast.on(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc);
585    this.appBroadCast.on(BroadCastConstants.UPDATE_DATA_SOURCE, this.onUpdateFavorStateFunc);
586    this.appBroadCast.on(BroadCastConstants.DO_ANIMATION, this.onDoAnimationFunc);
587
588    AppStorage.setOrCreate(Constants.PHOTO_GRID_SELECT_MANAGER, this.mSelectManager);
589    this.mSelectManager.registerCallback('allSelect', (newState: boolean) => {
590      Log.info(TAG, `allSelect ${newState}`);
591      if (this.isDataFreeze) {
592        return;
593      }
594      this.isAllSelected = newState;
595      this.dataSource.forceUpdate();
596    });
597    this.mSelectManager.registerCallback('updateCount', (newState: number) => {
598      Log.info(TAG, `updateSelectedCount ${newState}`);
599      if (this.isDataFreeze) {
600        return;
601      }
602      this.moreMenuList = Boolean(newState) ? (this.albumInfo.isSystemAlbum ?
603        [Action.ADD, Action.INFO] : [Action.MOVE, Action.ADD, Action.REMOVE_FROM, Action.INFO])
604        : (this.albumInfo.isSystemAlbum ? [Action.ADD_INVALID, Action.INFO_INVALID] :
605          [Action.MOVE_INVALID, Action.ADD_INVALID, Action.REMOVE_FROM_INVALID, Action.INFO_INVALID]);
606      this.totalSelectedCount = newState;
607    });
608    this.mSelectManager.registerCallback('select', (newState: number) => {
609      Log.info(TAG, `select ${newState}`);
610      if (this.isDataFreeze) {
611        return;
612      }
613      this.dataSource.onDataChanged(newState);
614    });
615    this.dataSource.registerCallback('updateCount', (newState: number) => {
616      Log.info(TAG, `updateTotalCount ${newState}`);
617      self.isShowScrollBar = (newState > Constants.PHOTOS_CNT_FOR_HIDE_SCROLL_BAR);
618      self.isEmpty = !Boolean(newState)
619      self.mSelectManager.setTotalCount(newState);
620    });
621
622    this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadedFunc);
623
624    ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack);
625
626    this.initGridRowCount();
627    this.moreMenuList = this.albumInfo.isSystemAlbum ?
628      [Action.ADD, Action.INFO] : [Action.MOVE, Action.ADD, Action.REMOVE_FROM, Action.INFO];
629    this.updateRightClickMenuList();
630    TraceControllerUtils.finishTrace('PhotoGridPageAboutToAppear');
631  }
632
633  private onDoAnimation(): void {
634    this.doAnimation();
635    this.appBroadCast.off(BroadCastConstants.DO_ANIMATION, undefined);
636  }
637
638  private select(position: number, key: string, value: boolean, callback: Function): void {
639    if (this.mSelectManager.toggle(key, value, position)) {
640      Log.info(TAG, 'enter event process')
641      if (!this.isSelectedMode) {
642        this.isSelectedMode = true;
643      }
644      callback();
645    }
646  }
647
648  private jumpPhotoBrowser(name: string, item: MediaItem,
649                           geometryTapIndex: number, geometryTransitionString: string): void {
650    let targetIndex = this.dataSource.getDataIndex(item);
651    if (targetIndex == Constants.NOT_FOUND) {
652      Log.error(TAG, 'targetIndex is not found');
653      return;
654    }
655    Log.info(TAG, `jump to photo browser at index: ${targetIndex}`);
656    let pageEntryFrom = Constants.ENTRY_FROM.NORMAL;
657    if (this.albumInfo.isTrashAlbum) {
658      pageEntryFrom = Constants.ENTRY_FROM.RECYCLE;
659    } else if (this.isDistributedAlbum) {
660      pageEntryFrom = Constants.ENTRY_FROM.DISTRIBUTED;
661    }
662
663    AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource);
664    if (geometryTapIndex !== undefined && geometryTransitionString !== undefined) {
665      this.jumpToPhotoBrowserGeometryTransition(
666        targetIndex, name, pageEntryFrom, geometryTapIndex, geometryTransitionString);
667    } else {
668      this.jumpToPhotoBrowserNormal(targetIndex, name, pageEntryFrom);
669    }
670  }
671
672  private jumpThirdPhotoBrowser(name: string, item: MediaItem,
673                                geometryTapIndex: number, geometryTransitionString: string): void {
674    Log.info(TAG, 'JUMP_THIRD_PHOTO_BROWSER');
675    let targetIndex = this.dataSource.getDataIndex(item);
676    if (targetIndex == Constants.NOT_FOUND) {
677      Log.error(TAG, 'targetIndex is not found');
678      return;
679    }
680    Log.info(TAG, `jump to photo browser at index: ${targetIndex} ${name}`);
681    let pageEntryFrom = Constants.ENTRY_FROM.NORMAL;
682    if (this.albumInfo.isTrashAlbum) {
683      pageEntryFrom = Constants.ENTRY_FROM.RECYCLE;
684    } else if (this.isDistributedAlbum) {
685      pageEntryFrom = Constants.ENTRY_FROM.DISTRIBUTED;
686    }
687    AppStorage.setOrCreate(Constants.PHOTO_GRID_SELECT_MANAGER, this.mSelectManager);
688    AppStorage.setOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource);
689    if (geometryTapIndex !== undefined && geometryTransitionString !== undefined) {
690      this.jumpToSelectPhotoBrowserGeometryTransition(
691        targetIndex, name, pageEntryFrom, geometryTapIndex, geometryTransitionString);
692    } else {
693      this.jumpToSelectPhotoBrowserNormal(targetIndex, name, pageEntryFrom);
694    }
695  }
696
697  private onLoadingFinished(size: number): void {
698    Log.info(TAG, `ON_LOADING_FINISHED size: ${size}`);
699  }
700
701  private onDataReloaded(): void {
702    Log.info(TAG, 'ON_DATA_RELOADED');
703    if (this.deleteMode) {
704      animateTo({ duration: 300 }, () => {
705        this.dataSource.onDataReloaded();
706      })
707      this.deleteMode = false;
708    } else {
709      this.dataSource.onDataReloaded();
710    }
711  }
712
713
714  updateFirstPhotoItemInfo(item: MediaItem, isFirstPhotoItem: boolean): void {
715    if (item) {
716      AppStorage.setOrCreate<boolean>(Constants.KEY_OF_IS_FIRST_PHOTO_ITEM, isFirstPhotoItem);
717      let albumUri = AppStorage.Get<string>(Constants.KEY_OF_ALBUM_URI);
718      let transitionId = `${item.hashCode}_${albumUri}`;
719      Log.info(TAG, `updateFirstPhotoItemInfo transitionId: ${transitionId}`);
720      AppStorage.Set<string>(Constants.KEY_OF_GEOMETRY_TRANSITION_ID_HEIGHT, transitionId);
721    }
722  }
723
724  jumpToPhotoBrowserNormal(targetIndex: number, name: string, pageEntryFrom: number) {
725    Log.debug(TAG, 'start jump to photo browser in normal');
726    router.pushUrl({
727      url: 'pages/PhotoBrowser',
728      params: {
729        position: targetIndex,
730        transition: name,
731        leftBlank: this.leftBlank,
732        pageFrom: pageEntryFrom,
733        deviceName: this.deviceName,
734        albumInfo: this.albumInfo
735      }
736    });
737  }
738
739  jumpToPhotoBrowserGeometryTransition(targetIndex: number, name: string, pageEntryFrom: number,
740                                       geometryTapIndex: number, geometryTransitionString: string) {
741    Log.debug(TAG, 'start jump to photo browser in geometry transition');
742
743    interface Params {
744      position: number;
745      transition: string;
746      leftBlank: number[];
747      pageFrom: number;
748      deviceName: string;
749      albumInfo: AlbumInfo;
750    }
751
752    let params: Params = {
753      position: targetIndex,
754      transition: name,
755      leftBlank: this.leftBlank,
756      pageFrom: pageEntryFrom,
757      deviceName: this.deviceName,
758      albumInfo: this.albumInfo
759    }
760    this.browserController.showBrowser(geometryTapIndex, geometryTransitionString, TAG, params);
761  }
762
763  jumpToSelectPhotoBrowserNormal(targetIndex: number, name: string, pageEntryFrom: number) {
764    Log.debug(TAG, 'start jump to select photo browser in normal');
765    router.pushUrl({
766      url: 'pages/SelectPhotoBrowser',
767      params: {
768        position: targetIndex,
769        transition: name,
770        pageFrom: pageEntryFrom,
771      }
772    });
773  }
774
775  jumpToSelectPhotoBrowserGeometryTransition(targetIndex: number, name: string, pageEntryFrom: number,
776                                             geometryTapIndex: number, geometryTransitionString: string) {
777    Log.debug(TAG, 'start jump to select photo browser in geometry transition');
778    interface Params {
779      position: number;
780      transition: string;
781      pageFrom: number;
782    }
783
784    const params: Params = {
785      position: targetIndex,
786      transition: name,
787      pageFrom: pageEntryFrom,
788    };
789    this.browserController.showSelectBrowser(geometryTapIndex, geometryTransitionString, TAG, params);
790  }
791
792  onMediaLibDataChange(changeType: string): void {
793    Log.info(TAG, `onMediaLibDataChange type: ${changeType}`);
794    this.dataSource.switchRefreshOn();
795    this.dataSource.onChange(changeType);
796  }
797
798  aboutToDisappear(): void {
799    Log.info(TAG, `aboutToDisappear`);
800    ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack);
801    this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc);
802    this.broadCast.off(BroadCastConstants.JUMP_PHOTO_BROWSER, this.jumpPhotoBrowserFunc);
803    this.broadCast.off(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc);
804    this.broadCast.off(Constants.ON_LOADING_FINISHED, this.onLoadingFinishedFunc);
805    this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED, this.onDataReloadedFunc);
806    this.appBroadCast.off(BroadCastConstants.UPDATE_DATA_SOURCE, this.onUpdateFavorStateFunc);
807    this.appBroadCast.off(BroadCastConstants.DO_ANIMATION, this.onDoAnimationFunc);
808    this.dataSource.releaseBroadCast();
809    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
810    this.dataObserver.clearSource();
811    this.mSelectManager?.releaseGetMediaItemFunc();
812    this.mSelectManager?.unregisterCallback('allSelect');
813    this.mSelectManager?.unregisterCallback('updateCount');
814    this.mSelectManager?.unregisterCallback('select');
815  }
816
817  isSameTransitionId(item: MediaItem): boolean {
818    return AppStorage.get<string>(Constants.KEY_OF_GEOMETRY_TRANSITION_ID_HEIGHT) as string ===
819      `${item.hashCode}_${this.dataSource.albumUri}`;
820  }
821
822  getGeometryTransitionId(item: ViewData): string {
823    return TAG + (item.mediaItem as MediaItem).hashCode +
824    this.mSelectManager.isItemSelected((item.mediaItem as MediaItem).uri);
825  }
826
827  build() {
828    Column() {
829      PhotoGridPageActionBar({
830        title: this.title,
831        albumInfo: this.albumInfo,
832        isSystemAlbum: this.albumInfo.isSystemAlbum,
833        onMenuClicked: (action: Action): void => this.onMenuClicked(action),
834        isRecycle: this.albumInfo.isTrashAlbum,
835        isDistributedAlbum: this.isDistributedAlbum,
836        totalSelectedCount: $totalSelectedCount
837      })
838        .opacity(this.photoGridActionBarOpacity)
839
840      if (this.isEmpty) {
841        NoPhotoComponent({ title: $r('app.string.no_distributed_photo_head_title_album') })
842      } else {
843        if (this.albumInfo.isTrashAlbum) {
844          Text($r('app.string.recycle_prompt_message', Constants.RECYCLE_DAYS_MAX))
845            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
846            .fontSize($r('sys.float.ohos_id_text_size_body2'))
847            .fontWeight(FontWeight.Regular)
848            .width(Constants.PERCENT_100)
849            .padding(this.isHorizontal ? {
850              left: $r('sys.float.ohos_id_max_padding_start'),
851              top: $r('app.float.recycle_prompt_message_margin_tb'),
852              bottom: $r('app.float.recycle_prompt_message_margin_tb')
853            } : {
854              left: $r('sys.float.ohos_id_max_padding_start'),
855              right: $r('sys.float.ohos_id_max_padding_end'),
856              top: $r('app.float.recycle_prompt_message_margin_tb'),
857              bottom: $r('app.float.recycle_prompt_message_margin_tb')
858            }
859            )
860        }
861        Stack() {
862          Grid(this.scroller) {
863            LazyForEach(this.dataSource, (item: ViewData, index?: number) => {
864              if (!!item) {
865                GridItem() {
866                  ImageGridItemComponent({
867                    dataSource: this.dataSource,
868                    item: item.mediaItem,
869                    isSelected: this.isSelectedMode ?
870                    this.mSelectManager.isItemSelected((item.mediaItem as MediaItem).uri) : false,
871                    isRecycle: this.albumInfo.isTrashAlbum,
872                    pageName: Constants.PHOTO_TRANSITION_ALBUM,
873                    onMenuClicked: (action: Action): void => this.onMenuClicked(action),
874                    onMenuClickedForSingleItem: (action: Action, currentPhoto: MediaItem): void => {
875                      this.onMenuClickedForSingleItem(action, currentPhoto);
876                    },
877                    geometryTransitionString: this.getGeometryTransitionId(item),
878                    mPosition: index,
879                    selectedCount: $totalSelectedCount
880                  })
881                }
882                .zIndex(this.isSameTransitionId(item.mediaItem as MediaItem) || index === this.placeholderIndex ? 1 : 0)
883                .width(this.gridItemWidth)
884                .aspectRatio(1)
885                .key('AlbumGridImage' + index)
886              }
887            }, (item: ViewData, index?: number) => {
888              if (item == null || item == undefined) {
889                return JSON.stringify(item);
890              }
891              // Update animation object
892              if (index === 0) {
893                if (this.scrollIndex === 0) {
894                  this.updateFirstPhotoItemInfo(item.mediaItem as MediaItem, true);
895                } else {
896                  this.updateFirstPhotoItemInfo((
897                    this.dataSource.getData(this.scrollIndex) as ViewData)?.mediaItem as MediaItem, false);
898                }
899              }
900              return this.getGeometryTransitionId(item)
901            })
902          }
903          .zIndex(-1)
904          .clip(false)
905          .onScrollIndex((index: number) => {
906            this.scrollIndex = index;
907            this.updateFirstPhotoItemInfo((this.dataSource.getData(index) as ViewData)?.mediaItem as MediaItem, false);
908          })
909          .edgeEffect(EdgeEffect.Spring)
910          .scrollBar(BarState.Off)
911          .columnsTemplate('1fr '.repeat(this.gridRowCount))
912          .columnsGap(Constants.GRID_GUTTER)
913          .rowsGap(Constants.GRID_GUTTER)
914          .cachedCount(Constants.GRID_CACHE_ROW_COUNT)
915          .transition(TransitionEffect.scale({
916            x: BrowserConstants.PHOTO_GRID_Scale,
917            y: BrowserConstants.PHOTO_GRID_Scale,
918            z: BrowserConstants.PHOTO_GRID_Scale
919          }))
920
921          if (this.isShowScrollBar) {
922            GridScrollBar({ scroller: this.scroller });
923          }
924
925          if (this.albumInfo.isTrashAlbum) {
926            Column() {
927              Row() {
928                Button({ type: ButtonType.Capsule, stateEffect: true }) {
929                  Text($r('app.string.action_clear_recycle'))
930                    .fontWeight(FontWeight.Medium)
931                    .fontSize($r('sys.float.ohos_id_text_size_button1'))
932                    .fontColor($r('sys.color.ohos_id_color_text_primary_activated'))
933                    .margin({
934                      left: $r('app.float.dialog_double_buttons_margin'),
935                      right: $r('app.float.dialog_double_buttons_margin')
936                    })
937                }
938                .key('ClearRecycleButton')
939                .height($r('app.float.details_dialog_button_height'))
940                .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
941                .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
942                .onClick(() => {
943                  this.onMenuClicked && this.onMenuClicked(Action.CLEAR_RECYCLE);
944                })
945              }
946              .borderRadius($r('sys.float.ohos_id_corner_radius_button'))
947              .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
948            }
949            .hitTestBehavior(HitTestMode.Transparent)
950            .width(`100%`)
951            .height('100%')
952            .alignItems(HorizontalAlign.Center)
953            .justifyContent(FlexAlign.End)
954            .padding({ bottom: $r('sys.float.ohos_id_default_padding_bottom_fixed') })
955            .visibility(this.isSelectedMode ? Visibility.Hidden : Visibility.Visible)
956          }
957        }
958        .clip(true)
959        .zIndex(-1)
960        .layoutWeight(1)
961        .padding({
962          bottom: ((this.isSelectedMode) && !this.isHorizontal) ? Constants.ActionBarHeight : 0
963        })
964      }
965      CustomDialogView({ broadCast: $broadCast });
966
967      if (this.isSelectedMode) {
968        PhotoGridPageToolBar({
969          onMenuClicked: (action: Action): void => this.onMenuClicked(action),
970          isRecycleAlbum: (this.albumInfo.isTrashAlbum),
971          isDistributedAlbum: this.isDistributedAlbum,
972          totalSelectedCount: $totalSelectedCount
973        });
974      }
975    }
976    .clip(true)
977    .opacity(this.photoGridViewOpacity)
978    .backgroundColor($r('app.color.default_background_color'))
979    .padding({
980      top: this.leftBlank[1]
981    })
982  }
983
984  private onUpdateFavorState(item: MediaItem): void {
985    Log.debug(TAG, 'onUpdateFavorState');
986    let index = this.dataSource.getIndexByMediaItem(item);
987    if (index != -1) {
988      this.dataSource.onDataChanged(index);
989    }
990  }
991
992  private moveOperation(albumName: string, albumUri: string): void {
993    let menuContext = new MenuContext();
994    if (this.isMvOrCpSeparatesItem) {
995      menuContext.withMediaItem(this.mvOrCpSeparatesItem);
996      this.onMoveStart && this.onMoveStart();
997    } else {
998      menuContext.withSelectManager(this.mSelectManager).withOperationStartCallback((): void => this.onMoveStart());
999    }
1000    menuContext.withOperationEndCallback((err: Error, count: number, total: number): void =>
1001    this.onMoveEnd(err as Object, count, total))
1002      .withBroadCast(this.broadCast)
1003      .withTargetAlbumName(albumName)
1004      .withAlbumUri(albumUri);
1005    let menuOperation = MenuOperationFactory.getInstance().createMenuOperation(MoveMenuOperation, menuContext);
1006    AppStorage.setOrCreate(Constants.APP_KEY_NEW_ALBUM_SOURCE, this.albumInfo.uri);
1007    menuOperation.doAction();
1008  }
1009
1010  private addOperation(albumName: string, albumUri: string): void {
1011    let menuContext = new MenuContext();
1012    if (this.isMvOrCpSeparatesItem) {
1013      menuContext.withMediaItem(this.mvOrCpSeparatesItem);
1014      this.onCopyStart && this.onCopyStart();
1015    } else {
1016      menuContext.withSelectManager(this.mSelectManager).withOperationStartCallback((): void => this.onCopyStart());
1017    }
1018    menuContext.withOperationEndCallback((err: Error, count: number, total: number): void =>
1019    this.onCopyEnd(err as Object, count, total))
1020      .withBroadCast(this.broadCast)
1021      .withTargetAlbumName(albumName)
1022      .withAlbumUri(albumUri);
1023    let menuOperation = MenuOperationFactory.getInstance().createMenuOperation(AddMenuOperation, menuContext);
1024    menuOperation.doAction();
1025  }
1026
1027  private onMenuClickedForSingleItem(action: Action, currentPhoto: MediaItem) {
1028    Log.info(TAG, `single menu click, action: ${action?.actionID}, currentUri: ${currentPhoto?.uri}`);
1029    if (currentPhoto == undefined) {
1030      return;
1031    }
1032    let menuOperation: MenuOperation;
1033    let menuContext: MenuContext;
1034    if (action.actionID === Action.RECOVER.actionID) {
1035      menuContext = new MenuContext();
1036      menuContext.withMediaItem(currentPhoto).withBroadCast(this.broadCast);
1037      menuOperation = MenuOperationFactory.getInstance()
1038        .createMenuOperation(RecoverMenuOperation, menuContext);
1039      menuOperation.doAction();
1040    } else if (action.actionID === Action.DELETE.actionID) {
1041      menuContext = new MenuContext();
1042      if (this.dataSource.albumUri == UserFileManagerAccess.getInstance()
1043        .getSystemAlbumUri(UserFileManagerAccess.TRASH_ALBUM_SUB_TYPE)) {
1044        menuContext.withAlbumUri(UserFileManagerAccess.getInstance()
1045          .getSystemAlbumUri(UserFileManagerAccess.TRASH_ALBUM_SUB_TYPE));
1046      }
1047      menuContext.withMediaItem(currentPhoto).withBroadCast(this.broadCast);
1048      menuOperation = MenuOperationFactory.getInstance()
1049        .createMenuOperation(DeleteMenuOperation, menuContext);
1050      menuOperation.doAction();
1051    } else if (action.actionID === Action.MOVE.actionID) {
1052      this.isMvOrCpSeparatesItem = true;
1053      this.mvOrCpSeparatesItem = currentPhoto;
1054      this.routeToSelectAlbumPage(MediaOperationType.Move, [currentPhoto]);
1055    } else if (action.actionID === Action.ADD.actionID) {
1056      this.isMvOrCpSeparatesItem = true;
1057      this.mvOrCpSeparatesItem = currentPhoto;
1058      this.routeToSelectAlbumPage(MediaOperationType.Add, [currentPhoto]);
1059    } else if (action.actionID === Action.REMOVE_FROM.actionID) {
1060      menuContext = new MenuContext();
1061      menuContext.withMediaItem(currentPhoto).withBroadCast(this.broadCast);
1062      menuOperation = MenuOperationFactory.getInstance()
1063        .createMenuOperation(RemoveMenuOperation, menuContext);
1064      menuOperation.doAction();
1065    } else if (action.actionID === Action.INFO.actionID) {
1066      this.broadCast.emit(BroadCastConstants.SHOW_DETAIL_DIALOG, [currentPhoto, false]);
1067    }
1068  }
1069
1070  private initGridRowCount(): void {
1071    let contentWidth = ScreenManager.getInstance().getWinWidth();
1072    let margin = 0;
1073    let maxThumbWidth = px2vp(Constants.GRID_IMAGE_SIZE) * Constants.GRID_MAX_SIZE_RATIO;
1074    // 原型机竖屏为4不变,横屏需计算: currentBreakpoint == 'lg' 表示横屏
1075    const currentBreakpoint: string = AppStorage.get<string>('currentBreakpoint') as string;
1076    this.gridRowCount = currentBreakpoint == Constants.BREAKPOINT_LG ?
1077    Math.max(Constants.GRID_MIN_COUNT, Math.ceil(((contentWidth - Constants.NUMBER_2 * margin) +
1078    Constants.GRID_GUTTER) / (maxThumbWidth + Constants.GRID_GUTTER))) : Constants.GRID_MIN_COUNT;
1079    this.gridItemWidth = (contentWidth - (this.gridRowCount - 1) * Constants.GRID_GUTTER -
1080      Constants.NUMBER_2 * margin) / this.gridRowCount;
1081    Log.info(TAG, `initGridRowCount contentWidth: ${contentWidth}`);
1082  }
1083
1084  private onUpdateRemoteDevice(res: string, deviceId: string): void {
1085    Log.info(TAG, `onUpdateRemoteDevice`);
1086    if (res == 'offline') {
1087      Log.debug(TAG, `device offline route to album main`);
1088      router.back({
1089        url: 'pages/index',
1090        params: {
1091          jumpSource: JumpSourceToMain.ALBUM,
1092        }
1093      });
1094    } else {
1095      Log.error(TAG, `res code is err ${res}`);
1096      return;
1097    }
1098  }
1099}
1100
1101interface Params {
1102  albumName: string;
1103  albumUri: string;
1104  pageType: string;
1105};
1106
1107interface ParamAlbumInfo {
1108  item: string;
1109  isFromFACard: boolean;
1110};