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