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(AppStorage.get<MediaDataSource>(Constants.APP_KEY_PHOTO_BROWSER) as MediaDataSource); 148 if (this.isMultiPick == true && this.selectManager) { 149 this.selectedCount = this.selectManager.getSelectedCount(); 150 } 151 this.onPhotoChanged(param.position); 152 this.mTransition = param.transition; 153 } catch (e) { 154 Log.error(TAG, `param error ${e}`); 155 } 156 this.dataSource.setBroadCast(this.broadCast); 157 this.broadCast.on(PhotoConstants.PULL_DOWN_END, this.pullDownEndFunc); 158 this.broadCast.on(PhotoConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc); 159 this.broadCast.on(PhotoConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc); 160 this.broadCast.on(PhotoConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc); 161 this.appBroadCast.on(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, this.pullDownEndFunc); 162 } 163 164 aboutToDisappear(): void { 165 this.broadCast.release(); 166 this.dataSource.release(); 167 mMultimodalInputManager.unregisterListener(); 168 if(this.broadCast) { 169 this.broadCast.off(PhotoConstants.PULL_DOWN_END, this.pullDownEndFunc); 170 this.broadCast.off(PhotoConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc); 171 this.broadCast.off(PhotoConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc); 172 this.broadCast.off(PhotoConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc); 173 } 174 this.appBroadCast.off(BroadCastConstants.SELECT_PHOTO_BROWSER_BACK_PRESS_EVENT, this.pullDownEndFunc); 175 } 176 177 onDataSizeChanged(size: number): void { 178 Log.info(TAG, `onDataSizeChanged, size is ${size}`); 179 if (size == 0) { 180 this.onBackPress(); 181 } 182 } 183 184 onPhotoChanged(index: number): void { 185 this.currentIndex = index; 186 this.timelineIndex = this.dataSource.getPositionByIndex(index); 187 let currentPhoto = this.getCurrentPhoto(); 188 if (currentPhoto == undefined) { 189 Log.error(TAG, 'onPhotoChanged, item is undefined'); 190 } else { 191 this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri, this.timelineIndex) ?? false; 192 AppStorage.SetOrCreate<number>('placeholderIndex', this.timelineIndex); 193 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 194 Log.info(TAG, `onPhotoChanged, index: ${index}, currentPhoto: ${currentPhoto.uri},\ 195 isSelected: ${this.isSelected} geometryTransitionId ${this.geometryTransitionId}`); 196 } 197 } 198 199 selectStateChange() { 200 Log.info(TAG, `change selected, timeline index ${this.timelineIndex}`); 201 let currentPhoto = this.getCurrentPhoto(); 202 if (currentPhoto == undefined) { 203 return; 204 } 205 this.isSelected = !this.isSelected; 206 if (this.selectManager?.toggle(currentPhoto.uri, this.isSelected, this.timelineIndex)) { 207 this.selectedCount = this.selectManager?.getSelectedCount() ?? 0; 208 } 209 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 210 Log.info(TAG, `selectedCount: ${this.selectedCount} after state change`) 211 } 212 213 onPageShow() { 214 Log.info(TAG, 'onPageShow'); 215 this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []); 216 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [true, this.mTransition]); 217 } 218 219 onPageHide() { 220 Log.info(TAG, 'onPageHide'); 221 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [false, this.mTransition]); 222 } 223 224 onMenuClicked(action: Action) { 225 Log.debug(TAG, `onMenuClicked, action: ${action.actionID}`); 226 if (action.actionID === Action.BACK.actionID) { 227 this.onBackPress(); 228 } else if (action.actionID === Action.MATERIAL_SELECT.actionID) { 229 Log.info(TAG, 'click UN_SELECTED'); 230 this.selectStateChange(); 231 } else if (action.actionID === Action.SELECTED.actionID) { 232 Log.info(TAG, 'click SELECTED'); 233 this.selectStateChange(); 234 } else if (action.actionID === Action.OK.actionID) { 235 Log.info(TAG, 'click OK'); 236 this.setPickResult(); 237 } 238 } 239 240 getCurrentPhoto(): MediaItem { 241 return this.dataSource.getData(this.currentIndex)?.data; 242 } 243 244 onBackPress() { 245 Log.info(TAG, 'onBackPress'); 246 if (this.geometryTransitionEnable) { 247 this.browserController.hideSelectBrowser(); 248 } else { 249 router.back({ 250 url: '', 251 params: { index: this.currentIndex } 252 }); 253 } 254 return true; 255 } 256 257 build() { 258 Stack({ alignContent: Alignment.TopStart }) { 259 PhotoBrowserBg({ isShowBar: $isShowBar }) 260 .opacity(this.geometryOpacity) 261 .transition(TransitionEffect.opacity(0)) 262 PhotoSwiper({ 263 dataSource: this.dataSource, 264 mTransition: this.mTransition, 265 onPhotoChanged: (index: number): void => this.onPhotoChanged(index), 266 swiperController: this.controller, 267 geometryTransitionEnable: this.geometryTransitionEnable, 268 broadCast: $broadCast, 269 isInSelectedMode: true, 270 isRunningAnimation: $isRunningAnimation 271 }) 272 273 ThirdSelectPhotoBrowserActionBar({ 274 isMultiPick: this.isMultiPick, 275 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 276 isShowBar: $isShowBar, 277 totalSelectedCount: $selectedCount 278 }) 279 .opacity(this.geometryOpacity) 280 .transition(TransitionEffect.opacity(0)) 281 } 282 } 283 284 pageTransition() { 285 PageTransitionEnter({ type: RouteType.None, duration: PhotoConstants.PAGE_SHOW_ANIMATION_DURATION }) 286 .opacity(0) 287 PageTransitionExit({ duration: PhotoConstants.PAGE_SHOW_ANIMATION_DURATION }) 288 .opacity(0) 289 } 290 291 private setPickResult(): void { 292 let uriArray: string[]; 293 if (this.isMultiPick) { 294 uriArray = SelectUtil.getUriArray(this.selectManager?.clickedSet ?? new Set()); 295 Log.debug(TAG, `uri size: ${uriArray}`); 296 } else { 297 let currentPhoto = this.getCurrentPhoto(); 298 if (currentPhoto == undefined) { 299 return; 300 } 301 uriArray = [currentPhoto.uri]; 302 } 303 let abilityResult: ability.AbilityResult = { 304 resultCode: 0, 305 want: { 306 parameters: { 307 'select-item-list': uriArray 308 } 309 } 310 }; 311 let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext') as common.UIAbilityContext; 312 context.terminateSelfWithResult(abilityResult).then((result: void) => { 313 Log.info(TAG, `terminateSelfWithResult result: ${result}`); 314 }); 315 } 316} 317