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 abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
17import app from '@system.app';
18import {
19  BigDataConstants,
20  BreakpointSystem,
21  BreakPointType,
22  BroadCast,
23  BroadCastConstants,
24  BroadCastManager,
25  BrowserConstants,
26  Constants,
27  JumpSourceToMain,
28  Log,
29  ReportToBigDataUtil,
30  ScreenManager,
31  TraceControllerUtils,
32  WindowUtil
33} from '@ohos/common';
34import {
35  BrowserController,
36  DeviceType,
37  TabBar,
38  TabItem
39} from '@ohos/common/CommonComponents';
40import router from '@ohos.router';
41import { PhotoGridView } from '../view/PhotoGridView';
42import { TabContentComponent } from '../view/TabContentComponent';
43import { TimelineTabContentComponent } from '../view/TimelineContentComponent';
44
45import data_preferences from '@ohos.data.preferences';
46import common from '@ohos.app.ability.common';
47import { BusinessError } from '@ohos.base';
48import { Router } from '@ohos.arkui.UIContext';
49
50export type Preferences = data_preferences.Preferences;
51
52const TAG: string = 'index';
53const COMPONENT_KEY_PHOTOS: string = 'Photos';
54const COMPONENT_KEY_ALBUMS: string = 'Albums';
55const IMAGE_CACHE_COUNT: number = 100;
56const IMAGE_CACHE_SIZE: number = 1024 * 1024 * IMAGE_CACHE_COUNT;
57
58interface Params {
59  jumpSource: number;
60};
61
62// Application entry
63@Entry
64@Component
65struct IndexPage {
66  @StorageLink('app_key_tabs_index') preIndex: number = Constants.TIMELINE_PAGE_INDEX;
67  @StorageLink('isSplitMode') isSplitMode: boolean = ScreenManager.getInstance().isSplitMode();
68  @StorageLink('leftBlank') leftBlank: number[] =
69    [
70  Constants.NUMBER_0,
71  ScreenManager.getInstance().getStatusBarHeight(),
72  Constants.NUMBER_0,
73  ScreenManager.getInstance().getNaviBarHeight()
74  ];
75  @StorageLink('isSidebar') isSidebar: boolean = ScreenManager.getInstance().isSidebar();
76  @Provide isShow: boolean = true;
77  @Provide screenHeight: number = 0.0;
78  @Provide screenWidth: number = 0.0;
79  @Provide isSelectedMode: boolean = false;
80  @Provide isAlbumSetSelectedMode: boolean = false;
81  @Provide isShowSideBar: boolean = false;
82  @State currentIndex: number = this.preIndex;
83  @StorageLink('entryFromHap') entryFromHap: number = Constants.ENTRY_FROM_NONE;
84  @State controlButtonMarginLeft: number = 16;
85  @StorageLink('deviceType') deviceType: string = AppStorage.get<string>('deviceType') as string;
86  @StorageLink('currentBreakpoint') @Watch('updateParameters') currentBreakpoint: string = Constants.BREAKPOINT_MD;
87  @StorageLink('isShowPhotoGridView') @Watch('doAnimation') isShowPhotoGridView: boolean = false;
88  @State isShowTabBar: boolean = true;
89  @State pageStatus: boolean = false;
90  @State isShowPhotoBrowser: boolean = false;
91  @State isShowSelectPhotoBrowser: boolean = false;
92  @State browserController: BrowserController = new BrowserController(true);
93  private tabs: TabItem[] = [
94    new TabItem($r('app.string.tab_timeline'), $r('app.media.ic_photos'), $r('app.media.ic_photos_actived'), false, $r('sys.color.ohos_id_color_bottom_tab_text_off'), COMPONENT_KEY_PHOTOS),
95    new TabItem($r('app.string.tab_albums'), $r('app.media.ic_albums'), $r('app.media.ic_albums_actived'), false, $r('sys.color.ohos_id_color_bottom_tab_text_off'), COMPONENT_KEY_ALBUMS)
96  ];
97  private tabsController: TabsController = new TabsController();
98  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
99  private jumpSource: number = 0;
100  private breakpointSystem: BreakpointSystem = new BreakpointSystem();
101  private photosPreferences: Preferences | null = null;
102  @State isShowBar: boolean = true;
103
104  doAnimation(): void {
105    if (this.isSidebar && this.currentBreakpoint !== Constants.BREAKPOINT_LG) {
106      this.isSidebar = this.isShowPhotoGridView ? false : true;
107    }
108    animateTo({
109      duration: this.isShowPhotoGridView ?
110      BrowserConstants.PHONE_LINK_IN_TAB_BAR_DURATION : BrowserConstants.PHONE_LINK_OUT_TAB_BAR_DURATION,
111      curve: Curve.Sharp
112    }, () => {
113      this.isShowTabBar = !this.isShowTabBar;
114    })
115  }
116
117  aboutToDisappear(): void {
118    Log.info(TAG, '[aboutToDisappear]');
119    this.breakpointSystem.unregister();
120  }
121
122  updateParameters(): void {
123    this.isSidebar = new BreakPointType({
124      sm: false,
125      md: false,
126      lg: true
127    }).getValue(this.currentBreakpoint);
128  }
129
130  initPhotosStore() {
131    this.photosPreferences = AppStorage.get<Preferences>(Constants.PHOTOS_STORE_KEY) as Preferences;
132    if (this.photosPreferences) {
133      try {
134        const data: data_preferences.ValueType = this.photosPreferences.getSync('lastPage', 0)
135        this.preIndex = data as number;
136        this.currentIndex = this.preIndex;
137      } catch (err) {
138        this.updatePhotosStore('lastPage', 0);
139      }
140    } else {
141      Log.info(TAG, 'photosPreferences is undefined');
142    }
143  }
144
145  updatePhotosStore(key: string, value: number): void {
146    if (this.photosPreferences) {
147      this.photosPreferences.put(key, value).then((): void => {
148        Log.debug(TAG, `Succeeded in putting the value of '${key}'.`);
149        this.photosPreferences?.flush();
150      }).catch((err: Error) => {
151        Log.error(TAG, `Failed to put the value of '${key}'. Cause: ${err}`);
152      });
153    }
154  }
155
156  aboutToAppear(): void {
157    TraceControllerUtils.startTrace('indexPageAppearToShow');
158    this.isShowBar = true;
159    ScreenManager.getInstance().setSystemUi(true);
160    this.breakpointSystem.register();
161    this.appBroadCast.on('hideBar', () => {
162      if (this.isShowBar) {
163        this.isShowBar = false;
164      } else {
165        this.isShowBar = true;
166      }
167    });
168    this.updateParameters();
169    let param: Params = router.getParams() as Params;
170    Log.info(TAG, `[aboutToAppear] param: ${JSON.stringify(param)}`);
171    this.requestPermissions();
172    if (param != null) {
173      this.jumpSource = param.jumpSource;
174      if (this.jumpSource == JumpSourceToMain.CAMERA) {
175        this.entryFromHap = Constants.ENTRY_FROM_NONE;
176        this.currentIndex = Constants.TIMELINE_PAGE_INDEX;
177        Log.info(TAG, `Camera in, switch to Tab ${this.currentIndex}.`);
178        interface Msg {
179          type: string;
180        }
181        let msg: Msg = {
182          type: BigDataConstants.ENTER_BY_CAMERA
183        };
184        ReportToBigDataUtil.report(BigDataConstants.ENTER_PHOTOS_ID, msg);
185      }
186    } else {
187      this.initPhotosStore();
188    }
189  }
190
191  onPageShow(): void {
192    Log.info(TAG, `[onPageShow] entryFromHap: ${this.entryFromHap}`);
193    if (typeof AppStorage.get<boolean | undefined>('IsSetImageRawDataCacheSize') === 'undefined') {
194      Log.info(TAG, '[onPageShow] setImageRawDataCacheSize');
195
196      // ImageCacheCount:缓存解码后的图片,默认为0
197      app.setImageCacheCount(IMAGE_CACHE_COUNT);
198      // ImageRawDataCache:缓存解码前的图片数据和缩略图的数据(datashare thumbnail格式)
199      app.setImageRawDataCacheSize(IMAGE_CACHE_SIZE);
200      AppStorage.setOrCreate<boolean>('IsSetImageRawDataCacheSize', true);
201    }
202    this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []);
203    setTimeout(() => {
204      this.isShow = true
205    }, 50);
206    let param: Params = router.getParams() as Params;
207    if (param != null) {
208      this.jumpSource = param.jumpSource;
209    }
210    Log.info(TAG, `router clear ${this.jumpSource}, routerLength=${router.getLength}`);
211    if (this.jumpSource == JumpSourceToMain.CAMERA) {
212      router.clear();
213    } else if (this.jumpSource == JumpSourceToMain.ALBUM) {
214      router.clear();
215
216      // To help AlbumSetPage show copy or move dialog
217      if (AppStorage.get<boolean>(Constants.IS_SHOW_MOVE_COPY_DIALOG)) {
218        this.appBroadCast.emit(BroadCastConstants.SEND_COPY_OR_MOVE_BROADCAST, [this.currentIndex]);
219        AppStorage.setOrCreate(Constants.IS_SHOW_MOVE_COPY_DIALOG, false);
220      }
221    }
222    this.pageStatus = true;
223    TraceControllerUtils.finishTrace('indexPageAppearToShow');
224  }
225
226  onPageHide(): void {
227    Log.info(TAG, '[onPageHide]');
228    this.pageStatus = false;
229    setTimeout(() => {
230      this.isShow = false
231    }, 50);
232  }
233
234  onBackPress(): boolean {
235    if (this.isShowPhotoBrowser) {
236      this.doPhotoBrowserViewBack();
237      return true;
238    }
239    if (this.isShowSelectPhotoBrowser) {
240      this.doSelectPhotoBrowserViewBack();
241      return true;
242    }
243    if (this.currentIndex === Constants.ALBUM_PAGE_INDEX) {
244      if (this.isShowPhotoGridView) {
245        if (this.isSelectedMode) {
246          this.isSelectedMode = !this.isSelectedMode;
247        } else {
248          this.appBroadCast.emit(BroadCastConstants.DO_ANIMATION, []);
249        }
250        return true;
251      } else {
252        if (this.isAlbumSetSelectedMode) {
253          this.isAlbumSetSelectedMode = !this.isAlbumSetSelectedMode;
254          return true;
255        } else {
256          return false;
257        }
258      }
259    }
260    let isProcessed = false;
261    this.appBroadCast.emit(BroadCastConstants.BACK_PRESS_EVENT,
262      [(isModeChanged: boolean): void => { isProcessed = isModeChanged; }]);
263    return isProcessed;
264  }
265
266  doSelectPhotoBrowserViewBack() {
267    this.appBroadCast.emit(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, []);
268  }
269
270  doPhotoBrowserViewBack() {
271    this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_BACK_PRESS_EVENT, []);
272  }
273
274  build() {
275    Row() {
276      if (this.entryFromHap == Constants.ENTRY_FROM_NONE) {
277        Stack() {
278          Tabs({
279            barPosition: BarPosition.Start,
280            index: this.currentIndex,
281            controller: this.tabsController
282          }) {
283            TabContent() {
284              Column() {
285                TimelineTabContentComponent({
286                  currentIndex: this.currentIndex,
287                  isShowTabBar: $isShowTabBar,
288                  isShowPhotoBrowser: $isShowPhotoBrowser,
289                  isShowSelectPhotoBrowser: $isShowSelectPhotoBrowser,
290                  pageStatus: this.pageStatus
291                })
292              }
293              .width('100%')
294              .height('100%')
295            }
296
297            TabContent() {
298              TabContentComponent({
299                currentIndex: this.currentIndex,
300                isShowTabBar: $isShowTabBar,
301                isShowPhotoBrowser: $isShowPhotoBrowser,
302                isShowSelectPhotoBrowser: $isShowSelectPhotoBrowser,
303                pageStatus: this.pageStatus
304              })
305            }
306          }
307          .animationDuration(Constants.NUMBER_0)
308          .vertical(true)
309          .scrollable(false)
310          .barMode(BarMode.Fixed)
311          .barWidth(Constants.NUMBER_0)
312          .barHeight(Constants.NUMBER_0)
313          .flexGrow(Constants.NUMBER_1)
314          .onChange((index: number) => {
315            AppStorage.setOrCreate<string>(Constants.KEY_OF_ALBUM_ID, '');
316            AppStorage.setOrCreate<string>(Constants.KEY_OF_ALBUM_URI, '');
317            this.resetTabState(this.currentIndex)
318            this.onTabChanged(index);
319            Log.info(TAG, `Switch to Tab ${this.currentIndex}.`)
320          })
321          .padding({ left: this.isSidebar ? $r('app.float.tab_bar_width') : Constants.NUMBER_0 })
322
323          TabBar({
324            currentIndex: $currentIndex,
325            tabs: this.tabs,
326            controller: this.tabsController,
327            isSidebar: $isSidebar,
328            deviceType: DeviceType.PHONE_LIKE
329          })
330            .visibility(this.isShowTabBar ? Visibility.Visible : Visibility.Hidden)
331          if (this.isShowPhotoGridView && this.currentBreakpoint == Constants.BREAKPOINT_LG) {
332            PhotoGridView({
333              pageStatus: this.pageStatus,
334              browserController: this.browserController
335            })
336              .transition(TransitionEffect.opacity(0.99))
337          }
338        }
339        .alignContent(Alignment.BottomStart)
340        .flexGrow(Constants.NUMBER_1)
341      }
342    }
343    .backgroundColor($r('app.color.default_background_color'))
344    .padding({
345      top: this.leftBlank[1],
346      bottom: this.isShowBar ? this.leftBlank[3] : this.leftBlank[1]
347    })
348
349  }
350
351  pageTransition() {
352    PageTransitionEnter({ duration: 300 })
353      .opacity(1)
354    PageTransitionExit({ duration: 300 })
355      .opacity(1)
356  }
357
358  // Reset the status of the removed tab. It is currently in the selection mode status (index is before switching)
359  private resetTabState(index: number): void {
360    this.appBroadCast.emit(BroadCastConstants.RESET_STATE_EVENT, [index]);
361  }
362
363  // Tab page switching callback (index after switching)
364  private onTabChanged(index: number): void {
365    this.updatePhotosStore('lastPage', index);
366    this.currentIndex = index;
367    this.preIndex = this.currentIndex;
368    this.appBroadCast.emit(BroadCastConstants.ON_TAB_CHANGED, [index]);
369  }
370
371  private async requestPermissions(): Promise<void> {
372    this.photosPreferences = AppStorage.get<Preferences>(Constants.PHOTOS_STORE_KEY) as Preferences;
373    let isRequested: boolean = false;
374    if (this.photosPreferences) {
375      isRequested = await this.photosPreferences.get(Constants.PHOTOS_PERMISSION_FLAG, false) as boolean;
376    } else {
377      Log.warn(TAG, 'photos preferences is undefined.');
378    }
379    Log.info(TAG, `Has permission been requested? ${isRequested}`);
380    if (!isRequested) {
381      let permissionList: string[] = [
382        'ohos.permission.READ_IMAGEVIDEO',
383        'ohos.permission.WRITE_IMAGEVIDEO',
384        'ohos.permission.MEDIA_LOCATION'
385      ];
386      let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
387      try {
388        atManager.requestPermissionsFromUser(
389          AppStorage.get<common.UIAbilityContext>('photosAbilityContext') as common.UIAbilityContext,
390          permissionList as Permissions[])
391          .then((data) => {
392          Log.debug(TAG, `permissions: ${JSON.stringify(data.permissions)}` +
393            `, authResult: ${JSON.stringify(data.authResults)}`);
394          let sum: number = 0;
395          for (let i = 0; i < data.authResults.length; i++) {
396            sum += data.authResults[i];
397          }
398          Log.info(TAG, `permissions sum: ${sum}`);
399          if (this.photosPreferences) {
400            this.photosPreferences.put(Constants.PHOTOS_PERMISSION_FLAG, true).then(() => {
401              Log.debug(TAG, `Succeeded in putting the value of '${Constants.PHOTOS_PERMISSION_FLAG}'.`);
402              this.photosPreferences?.flush();
403            }).catch((err: Error) => {
404              Log.error(TAG, `Failed to put the value of '${Constants.PHOTOS_PERMISSION_FLAG}'. Cause: ${err}`);
405            });
406          }
407        }, (err: BusinessError) => {
408          Log.error(TAG, `Failed to start ability err code: ${err.code}`);
409        });
410      } catch (error) {
411        Log.error(TAG, `catch error: ${JSON.stringify(error)}`);
412      }
413    }
414  }
415}