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 Matrix4 from '@ohos.matrix4';
18import {
19  Action,
20  BigDataConstants,
21  BroadCast,
22  BroadCastConstants,
23  BroadCastManager,
24  BrowserConstants,
25  Constants,
26  Log,
27  MediaDataSource,
28  MediaItem,
29  mMultimodalInputManager,
30  PhotoDataSource,
31  ReportToBigDataUtil,
32  ScreenManager,
33  SelectUtil,
34  ThirdSelectManager,
35  UiUtil,
36  BrowserDataFactory,
37  PhotoDataImpl,
38  UserFileManagerAccess,
39  MediaObserverNfyInfo
40} from '@ohos/common';
41import {
42  BrowserController,
43  PhotoBrowserBg,
44  PhotoSwiper,
45  ThirdSelectPhotoBrowserActionBar
46} from '@ohos/common/CommonComponents';
47
48import { FormConstants, IS_HORIZONTAL, LEFT_BLANK, SelectParams,
49  THIRD_SELECT_IS_ORIGIN } from '../utils/ThirdSelectConstants';
50import { ThirdSelectedPanel } from './ThirdSelectedPanel';
51import { MouseTurnPageOperation } from '@ohos/browser/BrowserComponents';
52import { Matrix4x4 } from '@ohos/common/src/main/ets/default/utils/Matrix4x4'
53import ability from '@ohos.ability.ability';
54import common from '@ohos.app.ability.common';
55import { Results } from '@ohos/common/src/main/ets/default/view/PhotoSwiper';
56import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
57import fileShare from '@ohos.fileshare';
58import wantConstant from '@ohos.ability.wantConstant';
59import { BusinessError } from '@ohos.base';
60import userFileManager from '@ohos.filemanagement.userFileManager';
61
62const TAG: string = 'thiSel_ThirdSelectPhotoBrowserBase';
63
64interface Params {
65  selectMode: boolean;
66  position: number;
67  transition: string;
68  bundleName: string;
69  title: string;
70  maxSelectCount: number;
71  isFromFa: boolean;
72  isJustSelected: boolean
73};
74
75// third selection photoBrowser
76@Component
77export struct ThirdSelectPhotoBrowserBase {
78  @Provide backgroundColorResource: Resource = $r('app.color.default_background_color');
79  @State totalSelectedCount: number = 0;
80  @Provide broadCast: BroadCast = new BroadCast();
81  @Provide isSelected: boolean = true;
82  @State isShowBar: boolean = true;
83  @Provide isDefaultBackgroundColor: boolean = true;
84  @State isPhotoScaled: boolean = false;
85  @Provide pageFrom: number = Constants.ENTRY_FROM.NORMAL;
86  selectManager: ThirdSelectManager | null = null;
87  bundleName: string = '';
88  isMultiPick = true;
89  mTransition: string = '';
90  controller?: SwiperController = new SwiperController();
91  @Provide('transitionIndex') currentIndex: number = 0;
92  @State currentUri: string = '';
93  isFromFa: boolean = false;
94  @Provide canSwipe: boolean = true;
95  // position
96  mPosition: number = 0;
97  @State title: string = '';
98  @Prop @Watch('onPageChanged') pageStatus: boolean = false;
99  @StorageLink(LEFT_BLANK) leftBlank: number[] =
100    [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()];
101  @StorageLink(IS_HORIZONTAL) isHorizontal: boolean = ScreenManager.getInstance().isHorizontal();
102  maxSelectCount: number = 0;
103  @StorageLink('geometryOpacity') geometryOpacity: number = 1;
104  @State @Watch('onGeometryChanged') geometryTransitionId: string = 'default_id';
105  @Link isRunningAnimation: boolean;
106  @ObjectLink browserController: BrowserController;
107  @Provide isDeleting: boolean = false;
108  // DataSource
109  private dataSource: ThirdBrowserDataSource = new ThirdBrowserDataSource();
110  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
111  private geometryTransitionEnable: boolean = false;
112  private isSelectMode: boolean = false;
113  @State isJustSelected: boolean = true;
114  private pullDownFunc: Function = (): Boolean => this.onBackPress();
115  private dataSizeChangedFunc: Function = (size: number): void => this.onDataSizeChanged(size);
116  private selectFunc: Function = (position: number, key: string, value: boolean): void =>
117  this.selectCallback(position, key, value);
118  private dataContentChangedFunc: Function = (index: number): void => this.onPhotoChanged(index);
119  private jumpThirdPhotoBrowserFunc: Function = (name: string, item: MediaItem, isSelectMode = false): void =>
120  this.jumpBrowserCallback(name, item, isSelectMode);
121  private setDisableSwipeFunc: Function = (value: boolean): void => this.setDisableSwipe(value);
122  newMediaItem: MediaItem | undefined = undefined;
123  onEnterEdit: Function | undefined = undefined;
124  canEditVideo: boolean = false;
125  private editNewUri: string = '';
126  private photoUnEdit: MediaItem | undefined = undefined;
127  @Provide canEdit: boolean = false;
128  private swiperDuration: number = 400;
129  private panelId: string = 'ThirdSelectPhotoBrowserBase';
130  private waitingUpdateIndex: number = -1;
131  private waitingUpdateData: boolean = false;
132  private isToEdit = false;
133  albumUri = '';
134  // 页面销毁时,ThirdSelectPhotoBrowserBase作为子组件可能仍未销毁,然后ThirdSelectPhotoBrowserBase在disappear时会调用
135  // refreshData刷新panel,这时会导致Crash,该变量用于识别这种情况,并组织刷新panel
136  private isPageDisappear: boolean = false;
137  allPhotoDataSource: MediaDataSource | undefined = undefined;
138  private funcOnDataReloadWithEdit: Function = async (): Promise<void> => await this.onDataReloadWithEdit();
139  private funcPageDisappear: Function = (): void => this.pageDisappear();
140  private funcUpdateEditItem: Function = (): void => this.updateEditItem();
141
142
143  updateEditItem(): void {
144    let currentPhoto = this.getCurrentPhoto();
145    this.photoUnEdit = currentPhoto;
146  }
147
148  onGeometryChanged() {
149    AppStorage.setOrCreate<string>('geometryTransitionBrowserId', this.geometryTransitionId);
150  }
151
152  aboutToAppear(): void {
153    AppStorage.setOrCreate('isReplace', true);
154    this.allPhotoDataSource = AppStorage.get<MediaDataSource>(Constants.APP_KEY_ALL_PHOTO_DATASOURCE);
155    Log.info(TAG, 'photoBrowser aboutToAppear');
156    this.backgroundColorResource = $r('app.color.black');
157    this.isDefaultBackgroundColor = false;
158    this.geometryTransitionId = AppStorage.get<string>('geometryTransitionBrowserId') as string;
159    this.browserController.browserBackFunc = (): boolean => this.onBackPress();
160    mMultimodalInputManager.registerListener((control: number) => {
161      Log.info(TAG, `key control : ${control} index ${this.currentIndex}`);
162      if (control == 0) {
163        if (this.currentIndex > 0) {
164          this.onPhotoChanged(this.currentIndex - 1);
165        }
166      } else if (control == 1) {
167        if (this.currentIndex < this.dataSource.totalCount() - 1) {
168          this.onPhotoChanged(this.currentIndex + 1);
169        }
170      } else {
171        this.onBackPress();
172      }
173    });
174    this.selectManager = AppStorage.get<ThirdSelectManager>(Constants.THIRD_SELECT_MANAGER) as ThirdSelectManager;
175    this.dataSource.setAlbumDataSource(
176      AppStorage.get<MediaDataSource>(Constants.APP_KEY_PHOTO_BROWSER) as MediaDataSource);
177    this.isMultiPick = this.selectManager.getIsMultiPick();
178    if (this.isMultiPick) {
179      this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
180    } else {
181      this.totalSelectedCount = 1;
182    }
183
184    let param: Params = this.browserController.browserParam as Params;
185    this.isFromFa = param.isFromFa;
186    this.isSelectMode = param.selectMode;
187    if (param.selectMode) {
188      this.dataSource.setSelectMode(this.selectManager);
189    }
190    this.onPhotoChanged(param.position);
191    this.photoUnEdit = this.getCurrentPhoto();
192    this.canEdit = AppStorage.get<boolean>(Constants.KEY_OF_IS_THIRD_EDITABLE) as boolean
193    this.mTransition = param.transition;
194    this.bundleName = param.bundleName;
195    this.title = param.title;
196    this.maxSelectCount = param.maxSelectCount;
197
198    this.dataSource.setBroadCast(this.broadCast);
199
200    this.broadCast.on(BrowserConstants.PULL_DOWN_END, this.pullDownFunc);
201    this.broadCast.on(BrowserConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc);
202    this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc);
203    this.broadCast.on(BrowserConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc);
204    this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc);
205    this.broadCast.on(BrowserConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc);
206    this.broadCast.on(BroadCastConstants.UPDATE_EDIT_ITEM, this.funcUpdateEditItem);
207    this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT, this.funcOnDataReloadWithEdit);
208    this.broadCast.on(BroadCastConstants.PICKER_PAGE_DISAPPEAR, this.funcPageDisappear);
209
210    this.dataSource.getAlbumDataSource()?.setPhotoBroadCast(this.broadCast);
211
212    if (this.pageStatus) {
213      this.onPageShow();
214    }
215  }
216
217  private pageDisappear(): void {
218    this.isPageDisappear = true;
219    this.onBackPress();
220  }
221
222  async onDataReloadWithEdit(): Promise<void> {
223    Log.info(TAG, 'BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT animate to data reloaded start with edit');
224    ReportToBigDataUtil.report(BigDataConstants.CREATE_THIRD_EDIT_SAVE, undefined);
225    try {
226      this.broadCast.emit(BroadCastConstants.CHANGE_SWIPER_DURATION, [0]);
227      let uri: string = AppStorage.get<string>(BroadCastConstants.PHOTO_EDIT_SAVE_URI) ?? '';
228
229      if (uri) {
230        // is in current album
231        let newIndex = this.dataSource.getDataIndexByUri(uri);
232
233        if (newIndex != Constants.NOT_FOUND) {
234          // Search for the position of new image/video after edit in current 500 items succeed
235          AppStorage.setOrCreate<number>('placeholderIndex', newIndex);
236          if (!this.isMultiPick) {
237            this.currentIndex = newIndex;
238            this.photoUnEdit = this.getCurrentPhoto();
239          } else {
240            let currentSelectIndex: number =
241              (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ??
242              Constants.INVALID;
243            this.currentIndex = newIndex;
244            if (currentSelectIndex !== -1) {
245              this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']);
246              this.unSelectEditPhoto();
247            }
248
249            if (this.totalSelectedCount < this.maxSelectCount) {
250              this.selectStateChangeEdit();
251            }
252
253            if (this.dataSource.getSelectMode() && this.selectManager) {
254              this.dataSource.setSelectMode(this.selectManager);
255            }
256            this.photoUnEdit = this.getCurrentPhotoInTimeLine();
257          }
258          this.photoChangedByMediaItem(this.getCurrentPhotoInTimeLine());
259        } else { // is not in current album or over 500
260          // Search for the position of new image/video after edit in current 500 items failed
261          this.canEdit = false;
262
263          this.editNewUri = uri;
264          this.dataSource.enableGetData(false);
265          this.currentIndex = 0;
266          this.dataSource.getItemIndexByUri(
267            this.editNewUri,
268            (index: number): void => this.onGetItemIndexByNewEditUri(index));
269        }
270      }
271    } catch (e) {
272      Log.error(TAG, `ON_DATA_RELOADED_WITH_EDIT error ${e}`);
273    } finally {
274      this.appBroadCast.emit(BroadCastConstants.PHOTO_EDIT_SAVE_COMPLETE, []);
275    }
276
277    this.dataSource.onDataReloaded();
278  }
279
280  photoChangedByMediaItem(mediaItem: MediaItem): void {
281    if (this.dataSource.getSelectMode()) {
282      this.currentIndex = this.dataSource.getDataIndex(mediaItem);
283    } else {
284      this.currentIndex = this.dataSource.getDataIndexByUri(this.photoUnEdit?.uri ?? '');
285      // 先暂时不更新编辑图片 this.photoUnEdit = mediaItem;
286    }
287
288    let currentPhoto = mediaItem;
289    this.canEdit = AppStorage.get<boolean>(Constants.KEY_OF_IS_THIRD_EDITABLE) as boolean &&
290    UiUtil.isEditedEnable(currentPhoto);
291
292    if (currentPhoto === undefined) {
293      Log.error(TAG, 'onPhotoChanged, item is undefined');
294    } else {
295      this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri) ?? false;
296      this.currentUri = currentPhoto.uri;
297
298      let dataSourceIndex = this.isSelectMode ?
299        (this.selectManager?.getSelectItemDataSourceIndex(currentPhoto) ?? Constants.INVALID) :
300      this.currentIndex;
301      let timelineIndex = this.dataSource.getPositionByIndex(dataSourceIndex);
302
303      if (this.geometryTransitionId !== undefined && this.geometryTransitionId !== '') {
304        AppStorage.setOrCreate<number>('placeholderIndex', timelineIndex as number);
305        this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected;
306        Log.info(TAG, `onPhotoChanged, index: ${this.currentIndex}, currentPhoto: ${currentPhoto.uri}, \
307        geometryTransitionId = ${this.geometryTransitionId}, placeholderIndex = ${timelineIndex}`);
308      }
309
310      if (this.totalSelectedCount < this.maxSelectCount) {
311        // 根据滑动方向 以及当前的位置 处理
312        this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_PANEL_INDEX, [this.currentUri]);
313
314        this.broadCast.emit(Constants.UPDATE_SELECTED, [true, this.currentUri]);
315      }
316    }
317  }
318
319  getCurrentPhotoInTimeLine(): MediaItem {
320    return this.dataSource.getDataInTimeLine(this.currentIndex)?.data;
321  }
322
323  selectStateChangeEdit(): void {
324    Log.info(TAG, 'change selected.');
325    let currentPhoto = this.getCurrentPhotoInTimeLine();
326    if (currentPhoto == undefined) {
327      return;
328    }
329    this.isSelected = true;
330    this.selectManager?.toggleEdit(currentPhoto.uri, true);
331    this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
332
333    this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [currentPhoto.uri, this.isSelected]);
334  }
335
336  onGetItemIndexByNewEditUri(index: number): void {
337    Log.info(TAG, `onGetItemIndexByNewEditUri: index=${index}`);
338    if (this.editNewUri.length > 0) {
339      if (index != Constants.NOT_FOUND) { // over 500
340        Log.info(TAG, `data reloaded move to ${index}`);
341
342        AppStorage.setOrCreate<number>('placeholderIndex', index);
343
344        this.dataSource.enableGetData(true);
345        this.dataSource.onDataReloaded();
346        let result: Results = this.dataSource.getDataInTimeLine(this.currentIndex);
347        if (result !== undefined) {
348          let mediaItem: MediaItem = result.data;
349          let pos: number = result.pos;
350          let thumbnail: string = result.thumbnail;
351          this.currentIndex = pos;
352          this.newMediaItem = mediaItem;
353          this.newMediaItem.setThumbnail(thumbnail);
354
355          this.currentUri = this.editNewUri;
356          this.editNewUri = '';
357
358          this.updateSelectItemByNewEditIndexFromDataSource(this.currentIndex);
359
360          if (this.dataSource.getSelectMode()) {
361            AppStorage.setOrCreate<number>('placeholderIndex', Constants.INVALID);
362          }
363        } else {
364          this.waitingUpdateIndex = index;
365          this.waitingUpdateData = true;
366        }
367      } else { // other album and can not save new in this album
368        Log.error(TAG, `edit new uri ${this.editNewUri} is invalid`);
369        this.dataSource.enableGetData(true);
370        this.dataSource.onDataReloaded();
371
372
373        // 此时数据应当使用选中列表的数据
374        let dataImpl: PhotoDataImpl | undefined =
375          BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO) as PhotoDataImpl | undefined;
376        dataImpl?.getDataByUri(this.editNewUri).then((fileAsset: userFileManager.FileAsset | undefined): void => {
377          this.newMediaItem = new MediaItem(fileAsset);
378          this.newMediaItem.setThumbnail(dataImpl?.getThumbnailSafe(this.newMediaItem.uri, this.newMediaItem.path));
379
380          this.editNewUri = '';
381          this.updateSelectItemByNewEditItem(this.newMediaItem, this.currentIndex);
382
383          AppStorage.setOrCreate<number>('placeholderIndex', Constants.INVALID);
384          this.geometryTransitionId = '';
385        });
386      }
387    }
388  }
389
390  updateSelectItemByNewEditItem(mediaItem: MediaItem, index?: number): void {
391    Log.info(TAG, `updateSelectItemByNewEditItem: index=${index}`);
392    AppStorage.setOrCreate<number>('placeholderIndex', index as number); // timeLineIndex
393    if (!this.isMultiPick) {
394      this.currentIndex = index as number;
395      this.photoUnEdit = this.getCurrentPhoto();
396    } else {
397      let currentSelectIndex =
398        (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ??
399        Constants.INVALID;
400      this.currentIndex = index as number;
401      if (currentSelectIndex !== -1) {
402        this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']);
403        this.unSelectEditPhoto();
404      }
405
406      if (this.totalSelectedCount < this.maxSelectCount) {
407        this.isSelected = true;
408        this.selectManager?.toggleEditThree(mediaItem.uri, true, mediaItem);
409        this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
410        this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [mediaItem.uri, this.isSelected]);
411      }
412
413      if (this.dataSource.getSelectMode() && this.selectManager) {
414        this.dataSource.setSelectMode(this.selectManager);
415        this.photoUnEdit = mediaItem;
416      }
417    }
418    this.photoChangedByMediaItem(mediaItem);
419    this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [this.photoUnEdit?.uri ?? '', this.isSelected]);
420  }
421
422  updateSelectItemByNewEditIndexFromDataSource(index: number): void {
423    Log.info(TAG, `updateSelectItemByNewEditIndexFromDataSource: index=${index}`);
424    AppStorage.setOrCreate<number>('placeholderIndex', index); // timeLineIndex
425    if (!this.isMultiPick) {
426      this.currentIndex = index;
427      this.photoUnEdit = this.getCurrentPhoto();
428    } else {
429      let currentSelectIndex =
430        (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ??
431        Constants.INVALID;
432      this.currentIndex = index;
433      if (currentSelectIndex !== -1) {
434        this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']);
435        this.unSelectEditPhoto();
436      }
437
438      if (this.totalSelectedCount < this.maxSelectCount) {
439        this.selectStateChangeEdit();
440      }
441
442      if (this.dataSource.getSelectMode() && this.selectManager) {
443        this.dataSource.setSelectMode(this.selectManager);
444      }
445      this.photoUnEdit = this.getCurrentPhotoInTimeLine();
446    }
447    this.photoChangedByMediaItem(this.getCurrentPhotoInTimeLine());
448    this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [this.photoUnEdit.uri, this.isSelected]);
449  }
450
451  unSelectEditPhoto(): void {
452    Log.info(TAG, 'unSelectEditPhoto.');
453    this.selectManager?.toggleEdit(this.photoUnEdit?.uri ?? '', false);
454    this.isSelected = false;
455    this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
456    Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change`);
457  }
458
459  onMediaLibDataNfy(nfyInfo: MediaObserverNfyInfo): void {
460    Log.info(TAG, `onMediaLibDataNfy nfyInfo: ${JSON.stringify(nfyInfo)}`);
461    this.dataSource.onDataReloaded();
462
463    if (this.allPhotoDataSource !== undefined && this.allPhotoDataSource !== null) {
464      this.allPhotoDataSource.onDataReloaded();
465    }
466  }
467
468  onMediaLibDataChange(changeType: string): void {
469    Log.info(TAG, `onMediaLibDataChange type: ${changeType}`);
470    this.dataSource.onDataReloaded();
471
472    if (this.allPhotoDataSource !== undefined && this.allPhotoDataSource !== null) {
473      this.allPhotoDataSource.onDataReloaded();
474    }
475  }
476
477  aboutToDisappear(): void {
478    Log.info(TAG, 'call aboutToDisappear');
479    // 数据清理以及重置
480    if (this.selectManager !== null) {
481      if (this.selectManager?.isPreview) {
482        this.selectManager.isPreview = false;
483        this.selectManager.clickedSet.clear();
484
485        this.selectManager.previewSet.forEach(
486          (value: string) => {
487            this.selectManager?.clickedSet.add(value);
488          });
489      }
490
491      this.selectManager?.selectedMap.forEach(
492        (value: MediaItem, key: string) => {
493          if (this.selectManager != null && !(this.selectManager.clickedSet.has(key))) {
494            if (value !== undefined) {
495              this.selectManager.indexMap.delete(value);
496            }
497            this.selectManager.selectedMap.delete(key);
498          }
499        });
500
501      // selectManager 多余数据处理
502      this.selectManager?.previewSet.clear();
503    }
504    this.selectManager?.refreshData();
505
506    this.broadCast.release();
507    if (this.broadCast) {
508      this.broadCast.off(BrowserConstants.PULL_DOWN_END, this.pullDownFunc);
509      this.broadCast.off(BrowserConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc);
510      this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc);
511      this.broadCast.off(BrowserConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc);
512      this.broadCast.off(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc);
513      this.broadCast.off(BrowserConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc);
514      this.broadCast.off(BroadCastConstants.UPDATE_EDIT_ITEM, this.funcUpdateEditItem);
515      this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT, this.funcOnDataReloadWithEdit);
516      this.broadCast.off(BroadCastConstants.PICKER_PAGE_DISAPPEAR, this.funcPageDisappear);
517    }
518    this.dataSource.release();
519    mMultimodalInputManager.unregisterListener();
520    this.controller = undefined;
521  }
522
523  onDataSizeChanged(size: number): void {
524    Log.info(TAG, `onDataSizeChanged, size is ${size}`);
525    if (size == 0) {
526      this.onBackPress();
527    }
528  }
529
530  setDisableSwipe(value: boolean): void {
531    Log.info(TAG, `set swiper swipe ${value}`);
532    this.canSwipe = value;
533  }
534
535  onPhotoChanged(index: number): void {
536    this.currentIndex = index;
537    let currentPhoto = this.getCurrentPhoto();
538    this.canEdit = (currentPhoto?.mediaType !== UserFileManagerAccess.MEDIA_TYPE_VIDEO);
539    if (currentPhoto === undefined) {
540      Log.error(TAG, 'onPhotoChanged, item is undefined');
541    } else {
542      this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri) ?? false;
543      this.currentUri = currentPhoto.uri;
544
545      let dataSourceIndex = this.isSelectMode ?
546        (this.selectManager?.getSelectItemDataSourceIndex(currentPhoto) ?? Constants.INVALID) : index;
547      let timelineIndex = this.dataSource.getPositionByIndex(dataSourceIndex);
548      AppStorage.setOrCreate<number>('placeholderIndex', timelineIndex);
549      this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected;
550      Log.info(TAG, `onPhotoChanged, index: ${index}, currentPhoto: ${currentPhoto.uri}, \
551        geometryTransitionId = ${this.geometryTransitionId}, placeholderIndex = ${timelineIndex}`);
552    }
553  }
554
555  selectStateChange() {
556    Log.info(TAG, 'change selected.');
557    let currentPhoto = this.getCurrentPhoto();
558    if (currentPhoto == undefined) {
559      return;
560    }
561    this.isSelected = !this.isSelected;
562    if (this.isSelected) {
563      this.selectManager?.toggle(currentPhoto.uri, true);
564    } else {
565      this.selectManager?.toggle(currentPhoto.uri, false);
566    }
567    this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
568    this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected;
569    this.broadCast.emit(BroadCastConstants.UPDATE_SELECT, [currentPhoto.uri, this.isSelected]);
570    Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change geometryTransitionId ${this.geometryTransitionId}`);
571  }
572
573  selectEditPhoto(newIdIndex: number) {
574    this.currentIndex = newIdIndex;
575    this.dataSource.resetSelectMode();
576    let currentPhoto = this.getCurrentPhoto();
577    if (currentPhoto == undefined) {
578      return;
579    }
580
581    this.isSelected = false;
582
583    this.selectManager?.toggle(this.photoUnEdit?.uri ?? '', false);
584
585    this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
586    Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change`);
587  }
588
589  selectCallback(position: number, key: string, value: boolean) {
590    if (key === this.currentUri) {
591      this.isSelected = value;
592    }
593    if (this.selectManager) {
594      this.selectManager.toggle(key, value);
595    }
596    this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0;
597    Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after select callback`);
598  }
599
600  onPageChanged() {
601    if (this.pageStatus) {
602      this.onPageShow();
603    } else {
604      this.onPageHide();
605    }
606  }
607
608  onPageShow() {
609    Log.debug(TAG, 'onPageShow');
610    this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []);
611    this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [true, this.mTransition]);
612  }
613
614  onPageHide() {
615    Log.debug(TAG, 'onPageHide');
616    this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [false, this.mTransition]);
617    // 数据清理以及重置
618
619    if (this.selectManager !== null) {
620      if (this.selectManager?.isPreview === true) {
621        this.selectManager.isPreview = false;
622        this.selectManager.clickedSet.clear();
623        for (let item of this.selectManager.previewSet) {
624          this.selectManager?.clickedSet.add(item);
625        }
626      }
627
628      this.selectManager?.selectedMap.forEach(
629        (value: MediaItem, key: string) => {
630          if (this.selectManager != null && !(this.selectManager.clickedSet.has(key))) {
631            if (value !== undefined) {
632              this.selectManager.indexMap.delete(value);
633            }
634            this.selectManager.selectedMap.delete(key);
635          }
636        });
637      // selectManager 多余数据处理
638      this.selectManager?.previewSet.clear();
639      this.selectManager?.refreshData();
640    }
641  }
642
643  onMenuClicked(action: Action) {
644    Log.info(TAG, `onMenuClicked, action: ${action.actionID}`);
645    if (action.actionID === Action.BACK.actionID) {
646      interface Msg {
647        from: string;
648      }
649      let msg: Msg = {
650        from: BigDataConstants.BY_CLICK,
651      }
652      ReportToBigDataUtil.report(BigDataConstants.ESC_PHOTO_BROWSER_WAY, msg);
653      this.onBackPress();
654    } else if (action.actionID === Action.MATERIAL_SELECT.actionID) {
655      Log.info(TAG, 'click UN_SELECTED');
656      this.selectStateChange();
657    } else if (action.actionID === Action.SELECTED.actionID) {
658      Log.info(TAG, 'click SELECTED');
659      this.selectStateChange();
660    } else if (action.actionID === Action.OK.actionID) {
661      Log.info(TAG, 'click OK');
662      this.setPickResult();
663    } else if (action.actionID === Action.EDIT.actionID) {
664      Log.info(TAG, 'click EDIT');
665      let currentPhoto = this.getCurrentPhoto();
666      if (currentPhoto == undefined || currentPhoto.size == 0) {
667        Log.warn(TAG, 'currentPhoto is undefined or size is 0.');
668        return;
669      }
670      AppStorage.setOrCreate<MediaItem | undefined>('EditorMediaItem', currentPhoto);
671      AppStorage.setOrCreate<string>('EditorAlbumUri', this.dataSource.getAlbumDataSource()?.albumUri);
672      router.pushUrl({
673        url: 'pages/EditMain'
674      })
675      this.isToEdit = true;
676    }
677  }
678
679  getCurrentPhoto(): MediaItem {
680    Log.debug(TAG, 'getCurrentPhoto  ' + this.currentIndex);
681    return this.dataSource.getData(this.currentIndex)?.data;
682  }
683
684  onBackPress() {
685    if (!this.isPageDisappear) {
686      this.selectManager?.refreshData();
687    }
688    if (this.geometryTransitionEnable) {
689      this.controller?.finishAnimation((): void => this.onBackPressInner());
690    } else {
691      router.back({
692        url: '',
693        params: { index: this.currentIndex }
694      });
695    }
696    return true;
697  }
698
699  @Builder
700  buildCheckBox() {
701    if (this.isMultiPick) {
702      Row() {
703        Image(this.isSelected ? $r('app.media.picker_checkbox_selected_dark') : $r('app.media.picker_checkbox_unselected_dark'))
704          .width($r('app.float.icon_size'))
705          .aspectRatio(1)
706          .key('Checkbox_' + this.currentIndex)
707          .margin({
708            right: $r('sys.float.ohos_id_max_padding_end'),
709            bottom: $r('app.float.picker_browser_checkbox_margin_bottom')
710          })
711          .onClick(() => {
712            this.selectStateChange();
713          })
714      }
715      .justifyContent(FlexAlign.End)
716      .width('100%')
717      .visibility(this.isShowBar ? Visibility.Visible : Visibility.Hidden)
718      .opacity(this.geometryOpacity)
719      .transition(TransitionEffect.opacity(0))
720      .hitTestBehavior(HitTestMode.Transparent)
721    }
722  }
723
724  @Builder
725  buildPanel() {
726    ThirdSelectedPanel({
727      maxSelectCount: this.maxSelectCount,
728      onMenuClicked: (action: Action): void => this.onMenuClicked(action),
729      isBrowserMode: true,
730      isMultiPick: this.isMultiPick,
731      mTransition: TAG,
732      isFromFa: this.isFromFa,
733      currentUri: this.currentUri,
734      isShowBar: $isShowBar,
735      totalSelectedCount: $totalSelectedCount
736    })
737      .opacity(this.geometryOpacity)
738      .transition(TransitionEffect.opacity(0))
739      .hitTestBehavior(HitTestMode.Transparent)
740  }
741
742  build() {
743    Stack({ alignContent: Alignment.Bottom }) {
744      Stack({ alignContent: Alignment.TopStart }) {
745        PhotoBrowserBg({ isShowBar: $isShowBar })
746          .opacity(this.geometryOpacity)
747          .transition(TransitionEffect.opacity(0))
748
749        PhotoSwiper({
750          dataSource: this.dataSource,
751          mTransition: this.mTransition,
752          onPhotoChanged: (index: number) => this.onPhotoChanged(index),
753          swiperController: this.controller,
754          verifyPhotoScaledFunc:  (matrix?: Matrix4.Matrix4Transit) => this.verifyPhotoScaled(matrix),
755          geometryTransitionEnable: true,
756          broadCast: $broadCast,
757          isRunningAnimation: $isRunningAnimation,
758          isInSelectedMode: true
759        })
760
761        if (this.isHorizontal) {
762          MouseTurnPageOperation({
763            dataSource: this.dataSource,
764            controller: this.controller,
765            isPhotoScaled: this.isPhotoScaled,
766            isShowBar: this.isShowBar
767          })
768            .opacity(this.geometryOpacity)
769            .transition(TransitionEffect.opacity(0))
770            .hitTestBehavior(HitTestMode.Transparent)
771
772        }
773        ThirdSelectPhotoBrowserActionBar({
774          isMultiPick: this.isMultiPick,
775          onMenuClicked: (action: Action): void => this.onMenuClicked(action),
776          title: this.title,
777          isThird: true,
778          isShowBar: $isShowBar,
779          totalSelectedCount: $totalSelectedCount
780        })
781          .opacity(this.geometryOpacity)
782          .transition(TransitionEffect.opacity(0))
783          .hitTestBehavior(HitTestMode.Transparent)
784      }
785
786      this.buildCheckBox()
787      this.buildPanel()
788    }
789    .padding({ bottom: this.leftBlank[3] })
790  }
791
792  pageTransition() {
793    PageTransitionEnter({ type: RouteType.None, duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION })
794      .opacity(0)
795    PageTransitionExit({ duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION })
796      .opacity(0)
797  }
798
799  verifyPhotoScaled(matrix?: Matrix4.Matrix4Transit): void {
800    if (matrix) {
801      let mat: number[] | undefined = (matrix.copy() as Matrix4x4).matrix4x4;
802      if (mat) {
803        let xScale: number = mat[0];
804        let yScale: number = mat[5];
805        Log.info(TAG, `photo in PhotoItem has Scaled x scale: ${xScale}, y scale: ${yScale}, mat: ${mat}`);
806        this.isPhotoScaled = xScale != 1 || yScale != 1
807      }
808    } else {
809      this.isPhotoScaled = false
810      Log.info(TAG, `photo in PhotoItem has not Scaled isPhotoScaled: ${this.isPhotoScaled}`);
811    }
812  }
813
814  private onBackPressInner(): void {
815    this.browserController.hideBrowser();
816  }
817
818  private jumpBrowserCallback(name: string, item: MediaItem, isSelectMode = false): void {
819    if (this.dataSource.getSelectMode() === false) {
820      if (this.selectManager) {
821        this.dataSource.setSelectMode(this.selectManager);
822      }
823      this.currentIndex = this.dataSource.getDataIndex(item);
824      this.onPhotoChanged(this.currentIndex);
825      this.dataSource.onDataReloaded();
826    } else {
827      if (this.dataSource && item && this.currentUri != item.uri) {
828        Log.debug(TAG, `jumpBrowserCallback jump to item.uri ${item.uri}, currentUri ${this.currentUri}`)
829        let tgtIndex = this.dataSource.getDataIndex(item);
830        Log.debug(TAG, `jump to index ${tgtIndex}`);
831        this.onPhotoChanged(tgtIndex);
832        this.dataSource.onDataReloaded();
833      }
834    }
835    this.photoUnEdit = item;
836  }
837
838  private setPickResult(): void {
839    if (this.isFromFa) {
840      let currentPhoto = this.getCurrentPhoto();
841      if (currentPhoto) {
842        Log.debug(TAG, `setPickResult. updateFormData obj: ${currentPhoto.uri}  currentIndex: ${this.currentIndex}`);
843        this.appBroadCast.emit(BroadCastConstants.SAVE_FORM_EDITOR_DATA,
844          ['', AppStorage.get<string>(FormConstants.FORM_ITEM_ALBUM_URI), AppStorage.get<Resource>(FormConstants.FORM_ITEM_DISPLAY_NAME),
845          currentPhoto.uri, false]);
846      } else {
847        Log.error(TAG, 'Fa setPickResult is null');
848      }
849      return;
850    }
851    let uriArray: string[] = [];
852    let mediaType: number;
853    if (this.isMultiPick) {
854      if (this.selectManager === null) {
855        Log.error(TAG, 'Select Manager empty');
856        return;
857      }
858      if (this.selectManager.isPreview) {
859        uriArray = SelectUtil.getUriArray(this.selectManager?.previewSet ?? new Set());
860      } else {
861        uriArray = SelectUtil.getUriArray(this.selectManager?.clickedSet ?? new Set());
862      }
863      Log.info(TAG, `uri size: ${uriArray}`);
864    } else {
865      if (!AppStorage.get('isReplace')) {
866        this.currentIndex = 0;
867      }
868      let currentPhoto = this.getCurrentPhoto();
869      if (currentPhoto == undefined) {
870        return;
871      }
872      uriArray = [currentPhoto.uri];
873    }
874    let promise: Promise<void> = SelectUtil.grantPermissionForUris(uriArray, this.bundleName);
875    let abilityResult: ability.AbilityResult = {
876      resultCode: 0,
877      want: {
878        parameters: {
879          'select-item-list': uriArray,
880        }
881      }
882    };
883    let localStorage = LocalStorage.getShared();
884    if (localStorage?.has(Constants.PHOTO_PICKER_SESSION_KEY)) {
885      let session = localStorage?.get<UIExtensionContentSession>(Constants.PHOTO_PICKER_SESSION_KEY);
886      let param = localStorage?.get<SelectParams>(Constants.PHOTO_PICKER_PARAMS_KEY);
887      if (uriArray === null || uriArray === undefined || uriArray?.length === 0) {
888        session?.terminateSelfWithResult(abilityResult).then((result: void) => {
889          Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`);
890        });
891      } else {
892        try {
893          if (param?.bundleName) {
894            Log.debug(TAG, `grantUriPermission to ${param?.bundleName}`);
895            uriArray.forEach(uri => {
896              fileShare.grantUriPermission(uri, param?.bundleName,
897                wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION,
898                (err: BusinessError): void => {
899                  Log.error(TAG, `failed to grantUriPermission to ${param?.bundleName}`);
900                  session?.terminateSelfWithResult(abilityResult).then((result: void) => {
901                    Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`);
902                  });
903                });
904            })
905          }
906        } catch (err) {
907          Log.error(TAG, `err: ${JSON.stringify(err)}`);
908          session?.terminateSelfWithResult(abilityResult).then((result: void) => {
909            Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`);
910          });
911        }
912      }
913    } else {
914      let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext') as common.UIAbilityContext;
915      context.terminateSelfWithResult(abilityResult).then((result: void) => {
916        Log.info(TAG, `terminateSelf result: ${result}, self result ${JSON.stringify(abilityResult)}`);
917      });
918    }
919    let selectedMap: Map<string, MediaItem> = this.selectManager?.selectedMap ?? new Map();
920    SelectUtil.getCountOfMedia(uriArray, selectedMap).then((result: number[]) => {
921      let isOrigin: boolean = AppStorage.get<boolean>(THIRD_SELECT_IS_ORIGIN) ?? false;
922      if (isOrigin == undefined) {
923        isOrigin = false;
924      }
925
926      interface Msg {
927        isOriginalChecked: boolean;
928        selectItemSize: number;
929        selectImageSize: number;
930        selectVideoSize: number;
931      }
932
933      let msg: Msg = {
934        isOriginalChecked: isOrigin,
935        selectItemSize: (uriArray === null || uriArray === undefined || uriArray.length <= 0) ? 0 : uriArray.length,
936        selectImageSize: this.isMultiPick ? result[0] : (mediaType === UserFileManagerAccess.MEDIA_TYPE_IMAGE ? 1 : 0),
937        selectVideoSize: this.isMultiPick ? result[1] : (mediaType === UserFileManagerAccess.MEDIA_TYPE_VIDEO ? 1 : 0)
938      }
939      ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_RESULT, msg);
940    });
941  }
942}
943
944/**
945 * 用于预览已选中的图片的dataSource
946 * 数据源取自selectManager的当前已选中图片
947 */
948class ThirdBrowserDataSource extends PhotoDataSource {
949  private isSelectMode = false;
950  private selectedItems: MediaItem[] = [];
951
952  totalCount() {
953    if (this.isSelectMode) {
954      return this.selectedItems.length;
955    }
956    return super.totalCount();
957  }
958
959  getData(index: number): Results {
960    if (this.isSelectMode) {
961      return this.packData(index, this.selectedItems[index]) as Results;
962    }
963    return super.getData(index) as Results;
964  }
965
966  setSelectMode(manager: ThirdSelectManager) {
967    this.isSelectMode = true;
968    this.selectedItems = manager.getSelectItems();
969  }
970
971  getDataIndex(item: MediaItem): number {
972    if (this.isSelectMode) {
973      for (let i = 0; i < this.selectedItems.length; i++) {
974        let clicked: MediaItem = this.selectedItems[i];
975        if (clicked.uri === item.uri) {
976          return i;
977        }
978      }
979      return Constants.NOT_FOUND;
980    }
981    return super.getDataIndex(item);
982  }
983
984  getDataInTimeLine(index: number): Results {
985    return super.getData(index) as Results;
986  }
987
988  getSelectMode(): boolean {
989    return this.isSelectMode;
990  }
991
992  resetSelectMode() {
993    this.isSelectMode = false;
994  }
995}
996