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 {
18  Action,
19  BroadCast,
20  BroadCastConstants,
21  BroadCastManager,
22  Constants,
23  Log,
24  MediaItem,
25  mMultimodalInputManager,
26  BrowserConstants as PhotoConstants,
27  PhotoDataSource,
28  ScreenManager,
29  SelectManager,
30  SelectUtil,
31  UiUtil,
32  MediaDataSource
33} from '@ohos/common';
34import {
35  BrowserController,
36  PhotoBrowserBg,
37  PhotoSwiper,
38  ThirdSelectPhotoBrowserActionBar
39} from '@ohos/common/CommonComponents';
40import ability from '@ohos.ability.ability';
41import common from '@ohos.app.ability.common';
42
43const TAG: string = 'SelectPhotoBrowserView';
44
45interface Params {
46  pageFrom: number;
47  deviceId: string;
48  position: number;
49  transition: string;
50};
51
52// select mode
53@Component
54export struct SelectPhotoBrowserView {
55  @Provide backgroundColorResource: Resource = $r('app.color.default_background_color');
56  @State selectedCount: number = 0;
57  @State broadCast: BroadCast = new BroadCast();
58  @Provide isSelected: boolean = true;
59  @State isShowBar: boolean = true;
60  @Provide pageFrom: number = Constants.ENTRY_FROM.NORMAL;
61  @Provide canSwipe: boolean = true;
62  selectManager: SelectManager | null = null;
63  isMultiPick = true;
64  mTransition: string = '';
65  controller: SwiperController = new SwiperController();
66  @Provide isDeleting: boolean = false;
67  @Provide canEdit: boolean = false;
68
69  // swiper currentIndex, there may not be onChanged callback during data refresh, so mediaItem cannot be saved
70  @Provide('transitionIndex') currentIndex: number = 0;
71
72  // position
73  mPosition: number = 0;
74  timelineIndex: number = -1;
75  @Prop @Watch('onPageChanged') pageStatus: boolean = false;
76  @StorageLink('geometryOpacity') geometryOpacity: number = 1;
77  @State @Watch('onGeometryChanged') geometryTransitionId: string = 'default_id';
78  @Link isRunningAnimation: boolean;
79  @ObjectLink browserController: BrowserController;
80  // dataSource
81  private dataSource: PhotoDataSource = new PhotoDataSource();
82  // The global BroadCast of the application process. Event registration and destruction should be paired
83  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
84  private geometryTransitionEnable: boolean = false;
85  private pullDownEndFunc: Function = (): void => this.pullDownEnd();
86  private dataSizeChangedFunc: Function = (size: number): void => this.onDataSizeChanged(size);
87  private dataContentChangedFunc: Function = (size: number): void => this.dataContentChanged(size);
88  private setDisableSwipeFunc: Function = (value: boolean): void => this.setDisableSwipe(value);
89
90  private pullDownEnd(): void {
91    this.onBackPress();
92  }
93
94  private dataContentChanged(size: number): void {
95    // onContentChanged only the current item is updated
96    Log.info(TAG, `DATA_CONTENT_CHANGED : ${size}`);
97    this.onPhotoChanged(this.currentIndex);
98  }
99
100  private setDisableSwipe(value: boolean): void {
101    Log.info(TAG, `set swiper swipe ${value}`);
102    this.canSwipe = value;
103  }
104
105  onPageChanged() {
106    if (this.pageStatus) {
107      this.onPageShow();
108    } else {
109      this.onPageHide();
110    }
111  }
112
113  onGeometryChanged() {
114    AppStorage.setOrCreate<string>('geometryTransitionBrowserId', this.geometryTransitionId);
115  }
116
117  aboutToAppear(): void {
118    Log.info(TAG, 'photoBrowser aboutToAppear');
119    this.geometryTransitionId = AppStorage.get<string>('geometryTransitionBrowserId') as string;
120    Log.info(TAG, `photoBrowser aboutToAppear  ${this.geometryTransitionId}`);
121    this.backgroundColorResource = $r('app.color.black');
122    mMultimodalInputManager.registerListener((control: number) => {
123      Log.info(TAG, `key control : ${control} index ${this.currentIndex}`);
124      if (control == 0) {
125        if (this.currentIndex > 0) {
126          this.onPhotoChanged(this.currentIndex - 1);
127        }
128      } else if (control == 1) {
129        if (this.currentIndex < this.dataSource.totalCount() - 1) {
130          this.onPhotoChanged(this.currentIndex + 1);
131        }
132      } else {
133        this.onBackPress();
134      }
135    });
136    this.selectManager = AppStorage.get<SelectManager>(Constants.PHOTO_GRID_SELECT_MANAGER) as SelectManager;
137    try {
138      let param: Params = this.browserController.selectBrowserParam as Params;
139      if (param.pageFrom) {
140        this.pageFrom = param.pageFrom;
141      }
142      if (this.pageFrom == Constants.ENTRY_FROM.RECYCLE) {
143        this.dataSource = new PhotoDataSource('Recycle');
144      } else if (this.pageFrom == Constants.ENTRY_FROM.DISTRIBUTED) {
145        this.dataSource.setDeviceId(param.deviceId);
146      }
147      this.dataSource.setAlbumDataSource(
148        AppStorage.get<MediaDataSource>(Constants.APP_KEY_PHOTO_BROWSER) as MediaDataSource);
149      if (this.isMultiPick == true && this.selectManager) {
150        this.selectedCount = this.selectManager.getSelectedCount();
151      }
152      this.onPhotoChanged(param.position);
153      this.mTransition = param.transition;
154    } catch (e) {
155      Log.error(TAG, `param error ${e}`);
156    }
157    this.dataSource.setBroadCast(this.broadCast);
158    this.broadCast.on(PhotoConstants.PULL_DOWN_END, this.pullDownEndFunc);
159    this.broadCast.on(PhotoConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc);
160    this.broadCast.on(PhotoConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc);
161    this.broadCast.on(PhotoConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc);
162    this.appBroadCast.on(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, this.pullDownEndFunc);
163  }
164
165  aboutToDisappear(): void {
166    this.broadCast.release();
167    this.dataSource.release();
168    mMultimodalInputManager.unregisterListener();
169    if (this.broadCast) {
170      this.broadCast.off(PhotoConstants.PULL_DOWN_END, this.pullDownEndFunc);
171      this.broadCast.off(PhotoConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc);
172      this.broadCast.off(PhotoConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc);
173      this.broadCast.off(PhotoConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc);
174    }
175    this.appBroadCast.off(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, this.pullDownEndFunc);
176  }
177
178  onDataSizeChanged(size: number): void {
179    Log.info(TAG, `onDataSizeChanged, size is ${size}`);
180    if (size == 0) {
181      this.onBackPress();
182    }
183  }
184
185  onPhotoChanged(index: number): void {
186    this.currentIndex = index;
187    this.timelineIndex = this.dataSource.getPositionByIndex(index);
188    let currentPhoto = this.getCurrentPhoto();
189    if (currentPhoto == undefined) {
190      Log.error(TAG, 'onPhotoChanged, item is undefined');
191    } else {
192      this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri, this.timelineIndex) ?? false;
193      AppStorage.setOrCreate<number>('placeholderIndex', this.timelineIndex);
194      this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected;
195      Log.info(TAG, `onPhotoChanged, index: ${index}, currentPhoto: ${currentPhoto.uri},\
196        isSelected: ${this.isSelected}  geometryTransitionId ${this.geometryTransitionId}`);
197    }
198  }
199
200  selectStateChange() {
201    Log.info(TAG, `change selected, timeline index ${this.timelineIndex}`);
202    let currentPhoto = this.getCurrentPhoto();
203    if (currentPhoto == undefined) {
204      return;
205    }
206    this.isSelected = !this.isSelected;
207    if (this.selectManager?.toggle(currentPhoto.uri, this.isSelected, this.timelineIndex)) {
208      this.selectedCount = this.selectManager?.getSelectedCount() ?? 0;
209    }
210    this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected;
211    Log.info(TAG, `selectedCount: ${this.selectedCount} after state change`)
212  }
213
214  onPageShow() {
215    Log.info(TAG, 'onPageShow');
216    this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []);
217    this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [true, this.mTransition]);
218  }
219
220  onPageHide() {
221    Log.info(TAG, 'onPageHide');
222    this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [false, this.mTransition]);
223  }
224
225  onMenuClicked(action: Action) {
226    Log.debug(TAG, `onMenuClicked, action: ${action.actionID}`);
227    if (action.actionID === Action.BACK.actionID) {
228      this.onBackPress();
229    } else if (action.actionID === Action.MATERIAL_SELECT.actionID) {
230      Log.info(TAG, 'click UN_SELECTED');
231      this.selectStateChange();
232    } else if (action.actionID === Action.SELECTED.actionID) {
233      Log.info(TAG, 'click SELECTED');
234      this.selectStateChange();
235    } else if (action.actionID === Action.OK.actionID) {
236      Log.info(TAG, 'click OK');
237      this.setPickResult();
238    }
239  }
240
241  getCurrentPhoto(): MediaItem {
242    return this.dataSource.getData(this.currentIndex)?.data;
243  }
244
245  onBackPress() {
246    Log.info(TAG, 'onBackPress');
247    if (this.geometryTransitionEnable) {
248      this.browserController.hideSelectBrowser();
249    } else {
250      router.back({
251        url: '',
252        params: { index: this.currentIndex }
253      });
254    }
255    return true;
256  }
257
258  build() {
259    Stack({ alignContent: Alignment.TopStart }) {
260      PhotoBrowserBg({ isShowBar: $isShowBar })
261        .opacity(this.geometryOpacity)
262        .transition(TransitionEffect.opacity(0))
263      PhotoSwiper({
264        dataSource: this.dataSource,
265        mTransition: this.mTransition,
266        onPhotoChanged: (index: number): void => this.onPhotoChanged(index),
267        swiperController: this.controller,
268        geometryTransitionEnable: this.geometryTransitionEnable,
269        broadCast: $broadCast,
270        isInSelectedMode: true,
271        isRunningAnimation: $isRunningAnimation
272      })
273
274      ThirdSelectPhotoBrowserActionBar({
275        isMultiPick: this.isMultiPick,
276        onMenuClicked: (action: Action): void => this.onMenuClicked(action),
277        isShowBar: $isShowBar,
278        totalSelectedCount: $selectedCount
279      })
280        .opacity(this.geometryOpacity)
281        .transition(TransitionEffect.opacity(0))
282    }
283  }
284
285  pageTransition() {
286    PageTransitionEnter({ type: RouteType.None, duration: PhotoConstants.PAGE_SHOW_ANIMATION_DURATION })
287      .opacity(0)
288    PageTransitionExit({ duration: PhotoConstants.PAGE_SHOW_ANIMATION_DURATION })
289      .opacity(0)
290  }
291
292  private setPickResult(): void {
293    let uriArray: string[];
294    if (this.isMultiPick) {
295      uriArray = SelectUtil.getUriArray(this.selectManager?.clickedSet ?? new Set());
296      Log.debug(TAG, `uri size: ${uriArray}`);
297    } else {
298      let currentPhoto = this.getCurrentPhoto();
299      if (currentPhoto == undefined) {
300        return;
301      }
302      uriArray = [currentPhoto.uri];
303    }
304    let abilityResult: ability.AbilityResult = {
305      resultCode: 0,
306      want: {
307        parameters: {
308          'select-item-list': uriArray
309        }
310      }
311    };
312    let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext') as common.UIAbilityContext;
313    context.terminateSelfWithResult(abilityResult).then((result: void) => {
314      Log.info(TAG, `terminateSelfWithResult result: ${result}`);
315    });
316  }
317}
318