• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import router from '@ohos.router';
17import {
18  Action,
19  AlbumDefine,
20  BigDataConstants,
21  BroadCast,
22  BroadCastConstants,
23  BroadCastManager,
24  BrowserController,
25  CommonObserverCallback,
26  Constants,
27  GridScrollBar,
28  ImageGridItemComponent,
29  Log,
30  MediaDataSource,
31  MediaItem,
32  MediaObserver,
33  NoPhotoIndexComponent,
34  PhotoDataImpl,
35  ReportToBigDataUtil,
36  ScreenManager,
37  SelectUtil,
38  ThirdSelectManager,
39  UiUtil,
40  UserFileManagerAccess,
41  ViewData,
42} from '@ohos/common';
43import { ThirdSelectedPageActionBar } from './ThirdSelectedPageActionBar';
44import { ThirdSelectedPanel } from './ThirdSelectedPanel';
45import { CameraGridItemComponent } from './CameraGridItemComponent';
46import {
47  FormConstants,
48  IS_HORIZONTAL,
49  IS_SPLIT_MODE,
50  LEFT_BLANK,
51  SelectParams,
52  THIRD_SELECT_IS_ORIGIN
53} from '../utils/ThirdSelectConstants';
54
55const TAG: string = 'thiSel_ThirdSelectPhotoGridBase';
56
57// Third Select Album Page
58@Component
59export struct ThirdSelectPhotoGridBase {
60  @State selectedCount: number = 0;
61  @Provide isSelectedMode: boolean = true;
62  @Provide moreMenuList: Array<Action> = new Array<Action>();
63  @Provide rightClickMenuList: Array<Action> = new Array<Action>();
64  PhotoDataImpl: PhotoDataImpl;
65  dataSource: MediaDataSource = new MediaDataSource(Constants.DEFAULT_SLIDING_WIN_SIZE);
66  @Provide broadCast: BroadCast = new BroadCast();
67  @Provide isShow: boolean = true;
68  selectManager: ThirdSelectManager;
69  isActive = false;
70  @State title: string = '';
71  @State isEmpty: boolean = false;
72  @StorageLink(IS_SPLIT_MODE) isSplitMode: boolean = ScreenManager.getInstance().isSplitMode();
73  @StorageLink(LEFT_BLANK) leftBlank: [number, number, number, number]
74    = [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()];
75  @StorageLink(IS_HORIZONTAL) isHorizontal: boolean = ScreenManager.getInstance().isHorizontal();
76  DEFAULT_TOAST_DURATION = 2000;
77  @State gridRowCount: number = 0;
78  @State isShowScrollBar: boolean = false;
79  @State currentUri: string = '';
80  @Provide isShowBar: boolean = true;
81  scroller: Scroller = new Scroller();
82  isFirstEnter: boolean = false;
83  @Prop @Watch('onPageChanged') pageStatus: boolean;
84  backFuncBinder: Function;
85  @State selectParams: SelectParams = SelectParams.defaultParam();
86  @State screenHeight: number = ScreenManager.getInstance().getWinHeight();
87  @StorageLink('placeholderIndex') @Watch('onPlaceholderChanged') placeholderIndex: number = -1;
88  @ObjectLink @Watch('onBrowserControllerChanged') browserController: BrowserController;
89  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
90  private dataObserver: CommonObserverCallback = new CommonObserverCallback(this);
91  private selectFromCameraFunc: () => void;
92  private itemId: string = undefined;
93  private isItemIdChange: boolean = false;
94  onWindowSizeChangeCallBack = () => this.initGridRowCount();
95
96  onPlaceholderChanged() {
97    Log.debug(TAG, 'onPlaceholderChanged placeholderIndex is ' + this.placeholderIndex);
98    if (this.placeholderIndex != -1) {
99      this.scroller.scrollToIndex(this.placeholderIndex);
100    }
101  }
102
103  onBrowserControllerChanged(): void {
104    if (!this.browserController.isBrowserShow) {
105      ScreenManager.getInstance().setSystemUi(true);
106    }
107  }
108
109  onMenuClicked(action: Action) {
110    Log.info(TAG, `onMenuClicked, action: ${action.actionID}`);
111    switch (action.actionID) {
112      case Action.BACK.actionID:
113        this.goBackFormEditor();
114        break;
115      case Action.CANCEL.actionID:
116        this.setPickResult(null);
117        break;
118      case Action.OK.actionID:
119        this.setPickResult(SelectUtil.getUriArray(this.selectManager.clickedSet));
120        break;
121      case Action.NAVIGATION_ALBUMS.actionID:
122        let params: any = this.selectParams;
123        params.isFirstEnter = false;
124        let options = {
125          url: 'pages/ThirdSelectAlbumSetPage',
126          params: params
127        }
128        router.pushUrl(options);
129        ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_SWITCH_ALBUM, null);
130        break;
131      default:
132        break;
133    }
134  }
135
136  aboutToAppear(): void {
137    let param: any = router.getParams();
138    this.initSelectParams(param);
139    if (this.selectParams.isFromFa) {
140      this.selectParams.filterMediaType = AlbumDefine.FILTER_MEDIA_TYPE_IMAGE;
141      AppStorage.SetOrCreate(FormConstants.FORM_ITEM_ALBUM_URI, param.uri);
142      AppStorage.SetOrCreate(FormConstants.FORM_ITEM_DISPLAY_NAME, param.itemDisplayName);
143    }
144    this.dataSource.setFilterMediaType(this.selectParams.filterMediaType);
145    this.initSelectManager();
146    this.selectManager.setIsMultiPick(this.selectParams.isMultiPick);
147
148    let self = this;
149    // 后续phone缩略图支持横竖屏后再放开
150    if (AppStorage.Get('deviceType') as string !== Constants.DEFAULT_DEVICE_TYPE) {
151      ScreenManager.getInstance().on(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack);
152    }
153    this.initGridRowCount();
154    this.onMenuClicked = this.onMenuClicked.bind(this);
155    this.dataSource.setBroadCast(this.broadCast);
156    this.broadCast.on(BroadCastConstants.SELECT, this.onSelectCallback.bind(this));
157    this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpBrowserCallback.bind(this));
158    this.broadCast.on(Constants.ON_LOADING_FINISHED,
159      (size: number) => {
160        this.onLoadFinishedCallback(size, this.updateTitle.bind(this, param));
161      });
162    this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED, this.onReloadFinishedCallback.bind(this));
163    this.selectManager.registerCallback('updateCount',
164      (newState: number) => {
165        Log.info(TAG, `updateSelectedCount ${newState}`);
166        self.selectedCount = newState;
167        self.selectManager.emitCallback('thirdSelectUpdateCount', [newState]);
168      });
169    this.dataSource.registerCallback('updateCount',
170      (newState: number) => {
171        Log.info(TAG, `updateTotalCount ${newState}`);
172        self.isShowScrollBar = (newState > Constants.PHOTOS_CNT_FOR_HIDE_SCROLL_BAR);
173        self.selectManager.setTotalCount(newState);
174      });
175    MediaObserver.getInstance().registerObserver(this.dataObserver);
176    this.isActive = true;
177
178    Log.error(TAG, 'meow data count = ' + this.dataSource.totalCount());
179  }
180
181  onBackPress() {
182    this.onMenuClicked(this.selectParams.isFromFa ? Action.BACK : Action.CANCEL);
183  }
184
185  onPageShow() {
186    Log.debug(TAG, 'onPageShow');
187    let param: any = router.getParams();
188    this.isItemIdChange = this.itemId && param && this.itemId !== param.itemId;
189    if (this.isItemIdChange) {
190      this.initSelectParams(param);
191    }
192
193    MediaObserver.getInstance().registerObserver(this.dataObserver);
194    this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []);
195    this.isShow = true;
196    if (!this.browserController.isBrowserShow) {
197      ScreenManager.getInstance().setSystemUi(true);
198    }
199    this.onActive();
200  }
201
202  onPageChanged() {
203    if (this.pageStatus) {
204      this.onPageShow();
205    } else {
206      this.onPageHide();
207    }
208  }
209
210  onPageHide() {
211    Log.debug(TAG, 'onPageHide');
212    this.isShow = false;
213    this.onInActive();
214  }
215
216  aboutToDisappear(): void {
217    ScreenManager.getInstance().off(ScreenManager.ON_WIN_SIZE_CHANGED, this.onWindowSizeChangeCallBack);
218    this.onWindowSizeChangeCallBack = null;
219    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
220    this.dataObserver.clearSource();
221    this.broadCast.off(null, null);
222    this.dataSource.releaseBroadCast();
223    Log.info(TAG, `call aboutToDisappear`)
224  }
225
226  onMediaLibDataChange(changeType) {
227    Log.info(TAG, `onMediaLibDataChange type: ${changeType}`);
228    this.dataSource.onChange(changeType);
229  }
230
231  getGeometryTransitionId(item: ViewData, index: number): string {
232    return TAG + item.mediaItem.getHashCode() + this.selectManager.isItemSelected(item?.mediaItem?.uri);
233  }
234
235  @Builder buildGrid() {
236    Grid(this.scroller) {
237      if (!this.selectParams.isFromFa) {
238        GridItem() {
239          CameraGridItemComponent({
240            selectParams: this.selectParams,
241            updateDataFunc: (uri: string) => {
242              Log.debug(TAG, `get camera callback, uri ${uri}`)
243              this.dataSource.initData();
244              this.selectFromCameraFunc = () => {
245                this.onSelectCallback(0, uri, true, null);
246              }
247            }
248          })
249        }
250        .aspectRatio(1)
251      }
252      LazyForEach(this.dataSource, (item, index?: number) => {
253        if (!!item) {
254          GridItem() {
255            ImageGridItemComponent({
256              dataSource: this.dataSource,
257              item: item?.mediaItem,
258              isSelected: this.selectManager.isItemSelected(item?.mediaItem?.uri),
259              pageName: Constants.PHOTO_TRANSITION_ALBUM,
260              isThird: true,
261              mPosition: item?.viewIndex,
262              isThirdMultiPick: this.selectParams.isMultiPick,
263              geometryTransitionString: this.getGeometryTransitionId(item, index),
264              selectedCount: $selectedCount
265            })
266          }
267          .aspectRatio(1)
268          .zIndex(index === this.placeholderIndex ? 1 : 0)
269        }
270      }, (item, index) => {
271        if (item == null || item == undefined) {
272          return JSON.stringify(item) + index;
273        }
274        return this.getGeometryTransitionId(item, index);
275      })
276    }
277    .edgeEffect(EdgeEffect.Spring)
278    .scrollBar(BarState.Off)
279    .gridStyle(this.gridRowCount)
280  }
281
282  build() {
283    Column() {
284      ThirdSelectedPageActionBar({
285        leftAction: this.selectParams.isFromFa ? Action.BACK : Action.CANCEL,
286        isSelectPhotoGrid: true,
287        title: $title,
288        selectParams: this.selectParams,
289        onMenuClicked: this.onMenuClicked,
290        isFirstEnter: this.isFirstEnter,
291        totalSelectedCount: $selectedCount
292      })
293
294      if (this.selectParams.isFromFa && this.isEmpty) {
295        NoPhotoIndexComponent({ index: Constants.TIMELINE_PAGE_INDEX, hasBarSpace: false })
296      }
297      Stack() {
298        this.buildGrid()
299        if (this.isShowScrollBar) {
300          GridScrollBar({ scroller: this.scroller });
301        }
302      }
303      .layoutWeight(1)
304
305      if (this.selectParams.isMultiPick) {
306        ThirdSelectedPanel({
307          maxSelectCount: this.selectParams.maxSelectCount,
308          onMenuClicked: this.onMenuClicked,
309          mTransition: TAG,
310          currentUri: this.currentUri,
311          isShowBar: $isShowBar,
312          totalSelectedCount: $selectedCount,
313          dataSource: this.dataSource
314        })
315      }
316    }
317    .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
318    .padding({
319      top: this.leftBlank[1],
320      bottom: this.leftBlank[3]
321    })
322  }
323
324  jumpToBrowserNormal(targetIndex: number, name: string, item: MediaItem, isSelectMode = false): void {
325    router.pushUrl({
326      url: 'pages/ThirdSelectPhotoBrowser',
327      params: {
328        position: targetIndex,
329        bundleName: this.selectParams.bundleName,
330        transition: name,
331        title: this.title,
332        selectMode: isSelectMode,
333        maxSelectCount: this.selectParams.maxSelectCount,
334        isFromFa: this.selectParams.isFromFa
335      }
336    });
337  }
338
339  jumpToBrowserGeometryTransition(targetIndex: number, name: string, item: MediaItem, isSelectMode = false,
340                                  geometryTapIndex: number = undefined,
341                                  geometryTransitionString: string = undefined): void {
342    this.browserController.showBrowser(geometryTapIndex, geometryTransitionString, TAG, {
343      position: targetIndex,
344      bundleName: this.selectParams.bundleName,
345      transition: name,
346      title: this.title,
347      selectMode: isSelectMode,
348      maxSelectCount: this.selectParams.maxSelectCount,
349      isFromFa: this.selectParams.isFromFa
350    });
351  }
352
353  private initGridRowCount(): void {
354    let contentWidth = ScreenManager.getInstance().getWinWidth();
355    let margin = 0;
356    let maxThumbWidth = px2vp(Constants.GRID_IMAGE_SIZE) * Constants.GRID_MAX_SIZE_RATIO;
357    let calCount = Math.round(
358      ((contentWidth - Constants.NUMBER_2 * margin) + Constants.GRID_GUTTER)
359      / (maxThumbWidth + Constants.GRID_GUTTER));
360    let newCount = Math.max(Constants.GRID_MIN_COUNT, calCount);
361    if (newCount != this.gridRowCount) {
362      this.gridRowCount = newCount;
363    }
364    Log.info(TAG, `initGridRowCount contentWidth: ${contentWidth}, row count ${this.gridRowCount}`);
365  }
366
367  private initSelectParams(param) {
368    if (param != null) {
369      this.isItemIdChange = this.itemId && this.itemId !== param.itemId;
370      this.itemId = param.itemId == undefined ? AlbumDefine.ALBUM_ID_ALL : param.itemId;
371      this.dataSource.setAlbumUri(this.itemId);
372
373      let albumUri = param.uri == undefined ?
374      UserFileManagerAccess.getInstance().getSystemAlbumUri(UserFileManagerAccess.IMAGE_ALBUM_SUB_TYPE) :
375      param.uri;
376      this.dataSource.setAlbumUri(albumUri);
377      this.updateTitle(param);
378      this.selectParams.bundleName = param.bundleName;
379      this.selectParams.isMultiPick = param.isMultiPick;
380      if (param.isFromFa != undefined || param.isFromFa != null) {
381        this.selectParams.isFromFa = param.isFromFa;
382      }
383      if (param.isFromFaPhoto != undefined || param.isFromFaPhoto != null) {
384        this.selectParams.isFromFaPhoto = param.isFromFaPhoto;
385      }
386      if (param.isFirstEnter != undefined || param.isFirstEnter != null) {
387        this.isFirstEnter = param.isFirstEnter;
388      }
389      if (!!param.filterMediaType) {
390        this.selectParams.filterMediaType = param.filterMediaType;
391      }
392      this.selectParams.isFromWallpaper = param.isFromWallpaper;
393      if (this.selectParams.isFromWallpaper) {
394        this.selectParams.maxSelectCount = param.remainingOfWallpapers;
395      } else if (!!param.maxSelectCount && param.maxSelectCount > 0) {
396        this.selectParams.maxSelectCount = param.maxSelectCount > Constants.LIMIT_MAX_THIRD_SELECT_COUNT
397          ? Constants.LIMIT_MAX_THIRD_SELECT_COUNT
398          : param.maxSelectCount;
399      }
400      if (this.backFuncBinder) {
401        this.backFuncBinder(this.onBackPress.bind(this));
402      }
403      Log.debug(TAG, `select param ${JSON.stringify(this.selectParams)}`);
404    }
405    this.isSelectedMode = this.selectParams.isMultiPick;
406    Log.debug(TAG, `select param ${JSON.stringify(this.selectParams)}, select mode ${this.isSelectedMode}`);
407  }
408
409  private updateTitle(param) {
410    let displayName = param.itemDisplayName == undefined ? $r('app.string.album_all') : param.itemDisplayName;
411    if (typeof displayName === 'object') {
412      UiUtil.getResourceString(displayName).then((stringResource) => {
413        this.title = stringResource;
414      })
415    } else {
416      this.title = displayName;
417    }
418    Log.debug(TAG, `update title ${this.title}`);
419  }
420
421  private onSelectCallback(position: number, key: string, value: boolean, callback: Function) {
422    Log.debug(TAG, `isHorizontal ${this.isHorizontal}, position ${position}, uri ${key}, select ${value}`)
423    let isMultiPick = this.selectParams.isMultiPick;
424    if (value && isMultiPick && this.selectedCount >= this.selectParams.maxSelectCount) {
425      if (!this.isHorizontal) {
426        UiUtil.showToast($r('app.string.up_to_limit_tips'));
427      }
428      return;
429    }
430    if (!isMultiPick) {
431      // update correct status from select manager
432      value = !this.selectManager.isItemSelected(key);
433      this.selectManager.deSelectAll();
434    }
435    if (this.selectManager.toggle(key, value, position)) {
436      Log.info(TAG, 'enter event process');
437      this.dataSource.onDataChanged(this.dataSource.getDataIndexByUri(key));
438      callback && callback(value);
439    }
440  }
441
442  private jumpBrowserCallback(name: string, item: MediaItem, geometryTapIndex: number = undefined,
443                              geometryTransitionString: string = undefined, isSelectMode = false) {
444    let targetIndex = isSelectMode ? this.selectManager.getSelectItemIndex(item) : this.dataSource.getDataIndex(item);
445    Log.info(TAG, `jump to photo browser at index: ${targetIndex}, transition: ${name}`);
446    AppStorage.SetOrCreate(Constants.APP_KEY_PHOTO_BROWSER, this.dataSource);
447    if (geometryTapIndex != undefined && geometryTransitionString != undefined) {
448      this.jumpToBrowserGeometryTransition(
449        targetIndex, name, item, isSelectMode, geometryTapIndex, geometryTransitionString);
450    } else {
451      this.jumpToBrowserNormal(targetIndex, name, item, isSelectMode);
452    }
453    ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_CLICK_PREVIEW, null);
454  }
455
456  private onReloadFinishedCallback() {
457    Log.info(TAG, 'ON_DATA_RELOADED');
458    this.dataSource.onDataReloaded();
459    this.selectFromCameraFunc && this.selectFromCameraFunc();
460    this.selectFromCameraFunc = undefined;
461  }
462
463  private onLoadFinishedCallback(size: number, updateTitle: Function) {
464    Log.info(TAG, `ON_LOADING_FINISHED size: ${size}`);
465    this.isEmpty = size == 0;
466    if (this.isEmpty && updateTitle) {
467      updateTitle();
468    }
469    Log.info(TAG, `isEmpty: ${this.isEmpty}`)
470    this.dataSource.onDataReloaded();
471  }
472
473  private initSelectManager() {
474    let manager: ThirdSelectManager = AppStorage.Get(Constants.THIRD_SELECT_MANAGER);
475    if (manager && manager.getClassName() === 'ThirdSelectManager') {
476      Log.debug(TAG, `use cached select manager, current select count ${manager.getSelectedCount()}`);
477      this.selectManager = manager;
478    } else {
479      Log.debug(TAG, 'create new select manager');
480      this.selectManager = new ThirdSelectManager();
481      AppStorage.SetOrCreate(Constants.THIRD_SELECT_MANAGER, this.selectManager);
482    }
483    if (this.isFirstEnter) {
484      Log.debug(TAG, 'clear select manager');
485      this.selectManager.deSelectAll();
486      AppStorage.SetOrCreate(THIRD_SELECT_IS_ORIGIN, false);
487    }
488    this.selectManager.setGetMediaItemFunc(this.dataSource.getMediaItemByUri.bind(this.dataSource));
489  }
490
491  private onActive() {
492    if (this.isItemIdChange) {
493      this.isActive = false;
494      this.dataSource && this.dataSource.initData();
495    }
496
497    if (!this.isActive) {
498      Log.info(TAG, 'onActive');
499      this.isActive = true;
500      this.dataSource && this.dataSource.onActive();
501      if (this.selectParams.isMultiPick) {
502        this.dataSource.forceUpdate();
503      }
504    }
505  }
506
507  private onInActive() {
508    if (this.isActive) {
509      Log.info(TAG, 'onInActive');
510      this.isActive = false;
511      this.dataSource && this.dataSource.onInActive();
512    }
513  }
514
515  private setPickResult(uriArray: Array<string>): void {
516    let isOrigin: boolean = AppStorage.Get(THIRD_SELECT_IS_ORIGIN);
517    if (isOrigin == undefined) {
518      isOrigin = false;
519    }
520    let abilityResult = {
521      'resultCode': 0,
522      'want': {
523        'parameters': {
524          'select-item-list': uriArray,
525          'isOriginal': isOrigin
526        }
527      }
528    };
529    let self = this;
530    let uriLength = 0;
531    if (uriArray == null && uriArray == undefined) {
532      globalThis.photosAbilityContext.terminateSelfWithResult(abilityResult).then((result) => {
533        Log.debug(TAG, `terminateSelfWithResult result: ${result}, self result ${JSON.stringify(abilityResult)}`);
534      });
535    } else {
536      uriLength = uriArray.length;
537      SelectUtil.grantPermissionForUris(uriArray, self.selectParams.bundleName).then(function () {
538        Log.info(TAG, `grant permission success.`);
539        globalThis.photosAbilityContext.terminateSelfWithResult(abilityResult).then((result) => {
540          Log.debug(TAG, `terminateSelfWithResult result: ${result}, self result ${JSON.stringify(abilityResult)}`);
541        });
542      }).catch(function (err) {
543        Log.error(TAG, `grant permission error: ${JSON.stringify(err)}, self result ${JSON.stringify(abilityResult)}`);
544      });
545    }
546    ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_RESULT,
547      { "isOriginalChecked": isOrigin, "selectItemSize": uriLength });
548  }
549
550  private goBackFormEditor() {
551    let formEditorOption = {
552      url: 'pages/FormEditorPage'
553    };
554    router.replaceUrl(formEditorOption);
555  }
556}
557
558@Extend(Grid) function gridStyle(gridCount: number) {
559  .columnsTemplate('1fr '.repeat(gridCount))
560  .columnsGap(Constants.GRID_GUTTER)
561  .rowsGap(Constants.GRID_GUTTER)
562  .cachedCount(Constants.GRID_CACHE_ROW_COUNT)
563  .layoutWeight(1)
564}
565