• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development 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 { MoveMenuOperation } from '../menus/MoveMenuOperation';
17import { SimpleAlbumDataItem } from '../common/SimpleAlbumDataItem';
18import { Constants } from '../constants/Constants';
19import { screenManager } from '../common/ScreenManager';
20import { Broadcast } from '../common/Broadcast';
21import { BroadcastConstants } from '../constants/BroadcastConstants';
22import { broadcastManager } from '../common/BroadcastManager';
23import { AlbumSetNewMenuOperation } from '../menus/AlbumSetNewMenuOperation';
24import { MenuContext } from '../menus/MenuContext';
25import { MenuOperation } from '../menus/MenuOperation';
26import { CustomDialogView } from './CustomDialogView';
27import { Action } from '../models/Action';
28import { ActionBar } from './ActionBar';
29import { ActionBarProp } from '../common/ActionBarProp';
30import { UserFileDataChangeCallback } from '../base/UserFileDataChangeCallback';
31import { CommonObserverCallback } from '../common/CommonObserverCallback';
32import { userFileObserver } from '../base/UserFileObserver';
33import { Log } from '../utils/Log';
34import { AlbumBarModel } from '../models/AlbumBarModel';
35import { ToolBar } from './ToolBar';
36import abilityAccessCtrl,{ PermissionRequestResult, Permissions} from '@ohos.abilityAccessCtrl';
37import { GlobalContext } from '../common/GlobalContext';
38import common from '@ohos.app.ability.common';
39import {
40  AlbumGridItemNewStyle
41} from './AlbumGridItemNewStyle';
42import {
43  AlbumSetDeleteMenuOperation
44} from '../menus/AlbumSetDeleteMenuOperation';
45import {
46  AlbumSetRenameMenuOperation
47} from '../menus/AlbumSetRenameMenuOperation';
48import { AlbumScrollBar } from './AlbumScrollBar';
49import { UserFileDataItem } from '../base/UserFileDataItem';
50import { AlbumsDataSource } from '../common/AlbumsDataSource';
51import { LazyItem } from '../common/ItemDataSource';
52import { AlbumDataItem } from '../common/AlbumDataItem';
53import { userFileModel } from '../base/UserFileModel';
54import { BusinessError } from '@ohos.base';
55
56// Album Set Page
57const TAG = 'AlbumSetPage'
58
59@Component
60export struct AlbumSetPage {
61  @Provide @Watch('onModeChange') isAlbumSetSelectedMode: boolean =false;
62  @Provide('selectedCount') @Watch('updateActionBar') selectedAlbumsCount: number = 0;
63  @Provide isHideScrollBar: boolean = true;
64  @State isEmpty: boolean = true;
65  @Provide gridColumnsCount: number = 0;
66  @Provide broadCast: Broadcast = new Broadcast();
67  @Consume @Watch('onIndexPageShow') isShow: boolean;
68  private dataObserver: CommonObserverCallback | null = new CommonObserverCallback(this as UserFileDataChangeCallback);
69  appBroadcast: Broadcast = broadcastManager.getBroadcast();
70  isActive = false; // Whether the page is in the foreground
71
72  scroller: Scroller | null = new Scroller();
73  @StorageLink('isSidebar') isSidebar: boolean = screenManager.isSidebar();
74
75  @Provide moreMenuList: Action[] = [];
76  private needNotify = false;
77  private barModel: AlbumBarModel | null = new AlbumBarModel();
78  private albumsDataSource: AlbumsDataSource = new AlbumsDataSource();
79  private isMediaLibDataChanged: boolean = true;
80
81  @State actionBarProp: ActionBarProp = new ActionBarProp();
82  @State toolBarMenuList: Action[] = [];
83
84  updateActionBar(): void {
85    if (this.barModel !== null) {
86      this.actionBarProp = this.barModel.createActionBar(
87        this.isAlbumSetSelectedMode,
88        this.selectedAlbumsCount,
89        this.albumsDataSource.isDisableRename(),
90        this.albumsDataSource.isDisableDelete(),
91      );
92
93      this.toolBarMenuList = this.barModel.getMenuList(
94        this.isAlbumSetSelectedMode,
95        this.selectedAlbumsCount,
96        this.albumsDataSource.isDisableRename(),
97        this.albumsDataSource.isDisableDelete());
98    }
99  }
100
101  onMenuClicked(action: Action, arg: Object[]): void {
102    this.onMenuClickedBindImpl(action, arg);
103  }
104
105  private onMenuClickedBindImpl(action: Action, arg: Object[]): void {
106    Log.info(TAG, 'onMenuClicked, action: ' + action.actionID);
107    let menuContext: MenuContext;
108    let menuOperation: MenuOperation;
109    if (action === Action.NEW) {
110      menuContext = new MenuContext();
111      menuContext
112        .withOperationStartCallback((): void => this.onOperationStartBindImpl())
113        .withOperationEndCallback((): void => this.onNewEndBindImpl())
114        .withDataSource(this.albumsDataSource)
115        .withBroadCast(this.broadCast);
116      menuOperation = new AlbumSetNewMenuOperation(menuContext);
117      menuOperation.doAction();
118    } else if (action === Action.CANCEL) {
119      this.isAlbumSetSelectedMode = false;
120    } else if (action === Action.RENAME) {
121      menuContext = new MenuContext();
122      menuContext
123        .withDataSource(this.albumsDataSource)
124        .withOperationStartCallback((): void => this.onOperationStartBindImpl())
125        .withOperationEndCallback((): void => this.onRenameEndBindImpl())
126        .withBroadCast(this.broadCast);
127      menuOperation = new AlbumSetRenameMenuOperation(menuContext);
128      menuOperation.doAction();
129    } else if (action === Action.DELETE) {
130      menuContext = new MenuContext();
131      menuContext
132        .withDataSource(this.albumsDataSource)
133        .withOperationStartCallback((): void => this.onOperationStartBindImpl())
134        .withOperationEndCallback((): void => this.onDeleteEndBindImpl())
135        .withBroadCast(this.broadCast);
136      menuOperation = new AlbumSetDeleteMenuOperation(menuContext);
137      menuOperation.doAction();
138    }
139  }
140
141  onOperationStart(): void {
142    this.onOperationStartBindImpl();
143  }
144
145  private onOperationStartBindImpl(): void {
146    Log.debug(TAG, 'onOperationStart');
147    userFileObserver.unregisterObserver(this.dataObserver);
148  }
149
150  private onDeleteEnd(): void {
151    this.onDeleteEndBindImpl();
152  }
153
154  private onDeleteEndBindImpl(): void {
155    this.isAlbumSetSelectedMode = false;
156    this.albumsDataSource.dataRemove();
157    userFileObserver.registerObserver(this.dataObserver);
158  }
159
160  onNewEnd(): void {
161    this.onNewEndBindImpl();
162  }
163
164  private onNewEndBindImpl(): void {
165    Log.debug(TAG, 'onNewEnd');
166    this.isAlbumSetSelectedMode = false;
167    userFileObserver.registerObserver(this.dataObserver);
168  }
169
170  onRenameEnd(): void {
171    this.onRenameEndBindImpl();
172  }
173
174  private onRenameEndBindImpl(): void {
175    Log.debug(TAG, 'onRenameEnd');
176    this.isAlbumSetSelectedMode = false;
177    this.albumsDataSource.notifyDataReload();
178    userFileObserver.registerObserver(this.dataObserver);
179  }
180
181  onOperationEnd(): void {
182    this.onOperationEndBindImpl();
183  }
184
185  private onOperationEndBindImpl(): void {
186    Log.debug(TAG, 'onOperationEnd');
187    this.isAlbumSetSelectedMode = false;
188    this.isMediaLibDataChanged = true;
189    this.selectedAlbumsCount = 0;
190    this.loadItem();
191    userFileObserver.registerObserver(this.dataObserver);
192    AppStorage.Delete(Constants.APP_KEY_NEW_ALBUM_SELECTED);
193  }
194
195  aboutToAppear(): void {
196    Log.info(TAG, 'AlbumSetPageAboutToAppear');
197    this.appBroadcast.on(BroadcastConstants.BACK_PRESS_EVENT, (clbk: Function): void => this.onIndexBackPressBindImpl(clbk));
198    this.appBroadcast.on(BroadcastConstants.RESET_ZERO, (pageNumber: number): void => this.onResetZeroBindImpl(pageNumber));
199
200    userFileObserver.registerObserver(this.dataObserver);
201
202    this.initGridRowCount();
203
204    let self = this;
205    this.broadCast.on(BroadcastConstants.SELECT, (index: number): void => this.onSelect(index));
206
207    screenManager.on(screenManager.ON_WIN_SIZE_CHANGED, (): void => {
208      self.initGridRowCount();
209    });
210    this.onIndexPageShow();	//TabContent uses lazy loading, call onIndexPageShow() the first time
211    this.updateActionBar();
212    this.getPermission();
213  }
214
215  aboutToDisappear(): void {
216    Log.info(TAG, 'aboutToDisappear');
217    this.broadCast.off(null, null);
218    this.appBroadcast.off(BroadcastConstants.BACK_PRESS_EVENT, (clbk: Function): void => this.onIndexBackPressBindImpl(clbk));
219    this.appBroadcast.off(BroadcastConstants.RESET_STATE_EVENT, (index: number): void => this.onStateResetBindImpl(index));
220    this.appBroadcast.off(BroadcastConstants.RESET_ZERO, (pageNumber: number): void => this.onResetZeroBindImpl(pageNumber));
221    userFileObserver.unregisterObserver(this.dataObserver);
222    this.dataObserver = null;
223    this.scroller = null;
224    this.barModel = null;
225  }
226
227  // Callback when the page is show.
228  onIndexPageShow(): void {
229    Log.info(TAG, 'onIndexPageShow');
230    if (this.isShow) {
231      this.onActive();
232      this.createAlbum();
233    } else if (!this.isShow) {
234      this.onInActive();
235    } else {
236      Log.info(TAG, 'other condition');
237    }
238  }
239
240  createAlbum(): void {
241    let newAlbum = AppStorage.Get<boolean>(Constants.APP_KEY_NEW_ALBUM);
242    if (newAlbum != null) {
243      AppStorage.Delete(Constants.APP_KEY_NEW_ALBUM);
244      this.moveOperation();
245    }
246  }
247
248  private moveOperation(): void {
249    this.moveOperationBindImpl();
250  }
251
252  private async moveOperationBindImpl(): Promise<void> {
253    let mediaItems = AppStorage.Get<UserFileDataItem[]>(Constants.APP_KEY_NEW_ALBUM_SELECTED);
254    let targetAlbum = AppStorage.Get<SimpleAlbumDataItem>(Constants.APP_KEY_NEW_ALBUM_TARGET);
255    if (mediaItems == undefined || undefined == targetAlbum) {
256      return;
257    }
258    let albumName = targetAlbum.displayName;
259    Log.info(TAG,'targetAlbum.displayName:'+albumName);
260    let newAlbum = await userFileModel.createAlbum(albumName);
261    if(newAlbum != undefined) {
262      Log.info(TAG,'  successfully, album: ' + newAlbum.displayName + ' album uri: ' + newAlbum.uri);
263      let menuContext = new MenuContext();
264      menuContext.withItems(mediaItems)
265        .withOperationStartCallback((): void => this.onOperationStartBindImpl())
266        .withOperationEndCallback((): void => this.onOperationEndBindImpl())
267        .withBroadCast(this.broadCast)
268        .withAlbumInfo(newAlbum);
269      let menuOperation = new MoveMenuOperation(menuContext);
270      menuOperation.doAction();
271    }
272  }
273
274  onModeChange(): void {
275    Log.info(TAG, 'onModeChange ' + this.isAlbumSetSelectedMode);
276    if (!this.isAlbumSetSelectedMode) {
277      this.albumsDataSource.setSelect(false);
278      this.selectedAlbumsCount = 0;
279    }
280    this.updateActionBar();
281  }
282
283  onIndexBackPress(callback: Function): void {
284    this.onIndexBackPressBindImpl(callback);
285  }
286
287  private onIndexBackPressBindImpl(callback: Function): void {
288    if (this.isAlbumSetSelectedMode) {
289      callback(true);
290      this.isAlbumSetSelectedMode = false;
291    } else {
292      callback(false);
293    }
294  }
295
296  onStateReset(index: number): void {
297    this.onStateResetBindImpl(index);
298  }
299
300  private onStateResetBindImpl(index: number): void {
301    if (index === Constants.ALBUM_PAGE_INDEX) {
302      this.isAlbumSetSelectedMode = false;
303    }
304  }
305
306  // Callback when the page is in the foreground
307  onActive(): void {
308    if (!this.isActive) {
309      Log.info(TAG, 'onActive');
310      this.isActive = true;
311      this.albumsDataSource.dataRemove();
312    }
313    this.loadItem();
314  }
315
316  // Callback when the page is in the background
317  onInActive(): void {
318    if (this.isActive) {
319      Log.info(TAG, 'onInActive');
320      this.isActive = false;
321    }
322  }
323
324  private onResetZeroBindImpl(pageNumber: number): void {
325    if (pageNumber === Constants.ALBUM_PAGE_INDEX) {
326      this.scroller?.scrollEdge(Edge.Top);
327    }
328  }
329
330  initGridRowCount(): void {
331    Log.info(TAG, 'get screen width is : ' + screenManager.getWinWidth());
332    Log.info(TAG, 'get screen height is : ' + screenManager.getWinHeight());
333
334    let sideBarWidth = this.isSidebar ? Constants.TAB_BAR_WIDTH : 0;
335
336    let contentWidth = screenManager.getWinWidth() - sideBarWidth;
337    let maxCardWidth = Constants.ALBUM_SET_COVER_SIZE * Constants.GRID_MAX_SIZE_RATIO;
338    this.gridColumnsCount = Math.ceil((contentWidth - Constants.ALBUM_SET_MARGIN * 2 + Constants.ALBUM_SET_GUTTER)
339    / (maxCardWidth + Constants.ALBUM_SET_GUTTER));
340    Log.info(TAG, 'the grid count in a line is: ' + this.gridColumnsCount);
341  }
342
343  onUserFileDataChange(changeType: string): void {
344    Log.info(TAG, 'onUserFileDataChange type: ' + changeType);
345    this.isMediaLibDataChanged = true;
346    this.albumsDataSource.resetLoadState();
347    this.loadItem();
348  }
349
350  private loadItem(): void {
351    if (this.isActive && this.isMediaLibDataChanged) {
352      this.albumsDataSource.reloadAlbumItemData().then<void, void>((isEmpty: boolean): void => {
353        this.isEmpty = isEmpty;
354        this.albumsDataSource.notifyDataReload();
355        this.isHideScrollBar = (this.albumsDataSource.totalCount() <= (this.gridColumnsCount * Constants.NUMBER_3 - Constants.NUMBER_1));
356      })
357    } else if (this.isActive) {
358      this.albumsDataSource.dataRemove();
359    }
360  }
361
362  private onSelect(index: number): void {
363    this.selectedAlbumsCount = this.albumsDataSource.getSelectedCount();
364    this.albumsDataSource.notifyDataChange(index);
365  }
366  getPermission() {
367    let array: Array<Permissions> = [
368      'ohos.permission.READ_IMAGEVIDEO',
369      'ohos.permission.WRITE_IMAGEVIDEO',
370    ];
371    let appContext: common.UIAbilityContext = GlobalContext.getContext().getObject('appContext') as common.UIAbilityContext;
372    let AtManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
373    //requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
374    AtManager.requestPermissionsFromUser(appContext, array).then((data:PermissionRequestResult) => {
375      Log.info(TAG,'data type:' + typeof(data));
376      Log.info(TAG,'data:' + data);
377      Log.info(TAG,'data permissions:' + data.permissions);
378      Log.info(TAG,'data result:' + data.authResults);
379      //reload
380      this.onUserFileDataChange('None');
381    }).catch((err:BusinessError) => {
382      Log.error(TAG,'Failed to start ability'+ err.code);
383    })
384  }
385
386  @Builder LocalAlbumSet() {
387    Stack() {
388      Grid(this.scroller) {
389        LazyForEach(this.albumsDataSource, (item: LazyItem<AlbumDataItem>): void => {
390          if ((item != undefined && item != null) && (item.get() != undefined && item.get() != null) && item.get().index === 0) {
391            GridItem() {
392              AlbumGridItemNewStyle({
393                lazyItem: item,
394                item: item.get(),
395                isBigCard: true,
396              })
397            }.columnStart(0).columnEnd(1)
398          } else if (item != null && item.get() != null) {
399            GridItem() {
400              AlbumGridItemNewStyle({
401                item: item.get(),
402                isBigCard: false,
403              })
404            }
405          }
406        }, (item: LazyItem<AlbumDataItem>): string => (item != undefined && item != null) && (item.get() != undefined && item.get() != null) ?
407          item.getHashCode() : JSON.stringify(item))
408      }
409      .columnsTemplate('1fr '.repeat(this.gridColumnsCount))
410      .padding({
411        left: $r('app.float.max_padding_start'),
412        right: $r('app.float.max_padding_end'),
413        top: $r('app.float.album_set_page_padding_top'),
414        bottom: (this.isSidebar ? $r('app.float.album_set_page_padding_end') : $r('app.float.album_set_page_padding_end_112'))
415      })
416      .columnsGap($r('app.float.album_set_grid_column_gap'))
417      .rowsGap($r('app.float.album_set_grid_row_gap'))
418
419      AlbumScrollBar({ scroller: this.scroller, hasSideBar: this.isSidebar })
420    }
421  }
422
423
424  build() {
425    Stack() {
426      Column() {
427        if (!this.isEmpty) {
428          ActionBar({
429            actionBarProp: $actionBarProp,
430            onMenuClicked: (action: Action, arg: Object[]): void => this.onMenuClicked(action, arg)
431          })
432          Column() {
433            this.LocalAlbumSet()
434          }
435        }
436        if (this.isAlbumSetSelectedMode) {
437          ToolBar({
438            toolMenuList: $toolBarMenuList,
439            onMenuClicked: (action: Action, arg: Object[]): void => this.onMenuClicked(action, arg)
440          })
441        }
442      }
443      .justifyContent(FlexAlign.Start)
444      .alignItems(HorizontalAlign.Start)
445
446      CustomDialogView()
447    }
448  }
449
450  pageTransition(): void {
451    PageTransitionExit({ type: RouteType.Push, duration: 1 })
452      .opacity(0)
453    PageTransitionEnter({ type: RouteType.Pop, duration: 1 })
454      .opacity(0)
455  }
456}
457