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