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}