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 router from '@ohos.router'; 17import Matrix4 from '@ohos.matrix4'; 18import { 19 Action, 20 BigDataConstants, 21 BroadCast, 22 BroadCastConstants, 23 BroadCastManager, 24 BrowserConstants, 25 Constants, 26 Log, 27 MediaDataSource, 28 MediaItem, 29 mMultimodalInputManager, 30 PhotoDataSource, 31 ReportToBigDataUtil, 32 ScreenManager, 33 SelectUtil, 34 ThirdSelectManager, 35 UiUtil, 36 BrowserDataFactory, 37 PhotoDataImpl, 38 UserFileManagerAccess, 39 MediaObserverNfyInfo 40} from '@ohos/common'; 41import { 42 BrowserController, 43 PhotoBrowserBg, 44 PhotoSwiper, 45 ThirdSelectPhotoBrowserActionBar 46} from '@ohos/common/CommonComponents'; 47 48import { FormConstants, IS_HORIZONTAL, LEFT_BLANK, SelectParams, 49 THIRD_SELECT_IS_ORIGIN } from '../utils/ThirdSelectConstants'; 50import { ThirdSelectedPanel } from './ThirdSelectedPanel'; 51import { MouseTurnPageOperation } from '@ohos/browser/BrowserComponents'; 52import { Matrix4x4 } from '@ohos/common/src/main/ets/default/utils/Matrix4x4' 53import ability from '@ohos.ability.ability'; 54import common from '@ohos.app.ability.common'; 55import { Results } from '@ohos/common/src/main/ets/default/view/PhotoSwiper'; 56import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 57import fileShare from '@ohos.fileshare'; 58import wantConstant from '@ohos.ability.wantConstant'; 59import { BusinessError } from '@ohos.base'; 60import userFileManager from '@ohos.filemanagement.userFileManager'; 61 62const TAG: string = 'thiSel_ThirdSelectPhotoBrowserBase'; 63 64interface Params { 65 selectMode: boolean; 66 position: number; 67 transition: string; 68 bundleName: string; 69 title: string; 70 maxSelectCount: number; 71 isFromFa: boolean; 72 isJustSelected: boolean 73}; 74 75// third selection photoBrowser 76@Component 77export struct ThirdSelectPhotoBrowserBase { 78 @Provide backgroundColorResource: Resource = $r('app.color.default_background_color'); 79 @State totalSelectedCount: number = 0; 80 @Provide broadCast: BroadCast = new BroadCast(); 81 @Provide isSelected: boolean = true; 82 @State isShowBar: boolean = true; 83 @Provide isDefaultBackgroundColor: boolean = true; 84 @State isPhotoScaled: boolean = false; 85 @Provide pageFrom: number = Constants.ENTRY_FROM.NORMAL; 86 selectManager: ThirdSelectManager | null = null; 87 bundleName: string = ''; 88 isMultiPick = true; 89 mTransition: string = ''; 90 controller?: SwiperController = new SwiperController(); 91 @Provide('transitionIndex') currentIndex: number = 0; 92 @State currentUri: string = ''; 93 isFromFa: boolean = false; 94 @Provide canSwipe: boolean = true; 95 // position 96 mPosition: number = 0; 97 @State title: string = ''; 98 @Prop @Watch('onPageChanged') pageStatus: boolean = false; 99 @StorageLink(LEFT_BLANK) leftBlank: number[] = 100 [0, ScreenManager.getInstance().getStatusBarHeight(), 0, ScreenManager.getInstance().getNaviBarHeight()]; 101 @StorageLink(IS_HORIZONTAL) isHorizontal: boolean = ScreenManager.getInstance().isHorizontal(); 102 maxSelectCount: number = 0; 103 @StorageLink('geometryOpacity') geometryOpacity: number = 1; 104 @State @Watch('onGeometryChanged') geometryTransitionId: string = 'default_id'; 105 @Link isRunningAnimation: boolean; 106 @ObjectLink browserController: BrowserController; 107 @Provide isDeleting: boolean = false; 108 // DataSource 109 private dataSource: ThirdBrowserDataSource = new ThirdBrowserDataSource(); 110 private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast(); 111 private geometryTransitionEnable: boolean = false; 112 private isSelectMode: boolean = false; 113 @State isJustSelected: boolean = true; 114 private pullDownFunc: Function = (): Boolean => this.onBackPress(); 115 private dataSizeChangedFunc: Function = (size: number): void => this.onDataSizeChanged(size); 116 private selectFunc: Function = (position: number, key: string, value: boolean): void => 117 this.selectCallback(position, key, value); 118 private dataContentChangedFunc: Function = (index: number): void => this.onPhotoChanged(index); 119 private jumpThirdPhotoBrowserFunc: Function = (name: string, item: MediaItem, isSelectMode = false): void => 120 this.jumpBrowserCallback(name, item, isSelectMode); 121 private setDisableSwipeFunc: Function = (value: boolean): void => this.setDisableSwipe(value); 122 newMediaItem: MediaItem | undefined = undefined; 123 onEnterEdit: Function | undefined = undefined; 124 canEditVideo: boolean = false; 125 private editNewUri: string = ''; 126 private photoUnEdit: MediaItem | undefined = undefined; 127 @Provide canEdit: boolean = false; 128 private swiperDuration: number = 400; 129 private panelId: string = 'ThirdSelectPhotoBrowserBase'; 130 private waitingUpdateIndex: number = -1; 131 private waitingUpdateData: boolean = false; 132 private isToEdit = false; 133 albumUri = ''; 134 // 页面销毁时,ThirdSelectPhotoBrowserBase作为子组件可能仍未销毁,然后ThirdSelectPhotoBrowserBase在disappear时会调用 135 // refreshData刷新panel,这时会导致Crash,该变量用于识别这种情况,并组织刷新panel 136 private isPageDisappear: boolean = false; 137 allPhotoDataSource: MediaDataSource | undefined = undefined; 138 private funcOnDataReloadWithEdit: Function = async (): Promise<void> => await this.onDataReloadWithEdit(); 139 private funcPageDisappear: Function = (): void => this.pageDisappear(); 140 private funcUpdateEditItem: Function = (): void => this.updateEditItem(); 141 142 143 updateEditItem(): void { 144 let currentPhoto = this.getCurrentPhoto(); 145 this.photoUnEdit = currentPhoto; 146 } 147 148 onGeometryChanged() { 149 AppStorage.setOrCreate<string>('geometryTransitionBrowserId', this.geometryTransitionId); 150 } 151 152 aboutToAppear(): void { 153 AppStorage.setOrCreate('isReplace', true); 154 this.allPhotoDataSource = AppStorage.get<MediaDataSource>(Constants.APP_KEY_ALL_PHOTO_DATASOURCE); 155 Log.info(TAG, 'photoBrowser aboutToAppear'); 156 this.backgroundColorResource = $r('app.color.black'); 157 this.isDefaultBackgroundColor = false; 158 this.geometryTransitionId = AppStorage.get<string>('geometryTransitionBrowserId') as string; 159 this.browserController.browserBackFunc = (): boolean => this.onBackPress(); 160 mMultimodalInputManager.registerListener((control: number) => { 161 Log.info(TAG, `key control : ${control} index ${this.currentIndex}`); 162 if (control == 0) { 163 if (this.currentIndex > 0) { 164 this.onPhotoChanged(this.currentIndex - 1); 165 } 166 } else if (control == 1) { 167 if (this.currentIndex < this.dataSource.totalCount() - 1) { 168 this.onPhotoChanged(this.currentIndex + 1); 169 } 170 } else { 171 this.onBackPress(); 172 } 173 }); 174 this.selectManager = AppStorage.get<ThirdSelectManager>(Constants.THIRD_SELECT_MANAGER) as ThirdSelectManager; 175 this.dataSource.setAlbumDataSource( 176 AppStorage.get<MediaDataSource>(Constants.APP_KEY_PHOTO_BROWSER) as MediaDataSource); 177 this.isMultiPick = this.selectManager.getIsMultiPick(); 178 if (this.isMultiPick) { 179 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 180 } else { 181 this.totalSelectedCount = 1; 182 } 183 184 let param: Params = this.browserController.browserParam as Params; 185 this.isFromFa = param.isFromFa; 186 this.isSelectMode = param.selectMode; 187 if (param.selectMode) { 188 this.dataSource.setSelectMode(this.selectManager); 189 } 190 this.onPhotoChanged(param.position); 191 this.photoUnEdit = this.getCurrentPhoto(); 192 this.canEdit = AppStorage.get<boolean>(Constants.KEY_OF_IS_THIRD_EDITABLE) as boolean 193 this.mTransition = param.transition; 194 this.bundleName = param.bundleName; 195 this.title = param.title; 196 this.maxSelectCount = param.maxSelectCount; 197 198 this.dataSource.setBroadCast(this.broadCast); 199 200 this.broadCast.on(BrowserConstants.PULL_DOWN_END, this.pullDownFunc); 201 this.broadCast.on(BrowserConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc); 202 this.broadCast.on(BroadCastConstants.SELECT, this.selectFunc); 203 this.broadCast.on(BrowserConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc); 204 this.broadCast.on(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 205 this.broadCast.on(BrowserConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc); 206 this.broadCast.on(BroadCastConstants.UPDATE_EDIT_ITEM, this.funcUpdateEditItem); 207 this.broadCast.on(BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT, this.funcOnDataReloadWithEdit); 208 this.broadCast.on(BroadCastConstants.PICKER_PAGE_DISAPPEAR, this.funcPageDisappear); 209 210 this.dataSource.getAlbumDataSource()?.setPhotoBroadCast(this.broadCast); 211 212 if (this.pageStatus) { 213 this.onPageShow(); 214 } 215 } 216 217 private pageDisappear(): void { 218 this.isPageDisappear = true; 219 this.onBackPress(); 220 } 221 222 async onDataReloadWithEdit(): Promise<void> { 223 Log.info(TAG, 'BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT animate to data reloaded start with edit'); 224 ReportToBigDataUtil.report(BigDataConstants.CREATE_THIRD_EDIT_SAVE, undefined); 225 try { 226 this.broadCast.emit(BroadCastConstants.CHANGE_SWIPER_DURATION, [0]); 227 let uri: string = AppStorage.get<string>(BroadCastConstants.PHOTO_EDIT_SAVE_URI) ?? ''; 228 229 if (uri) { 230 // is in current album 231 let newIndex = this.dataSource.getDataIndexByUri(uri); 232 233 if (newIndex != Constants.NOT_FOUND) { 234 // Search for the position of new image/video after edit in current 500 items succeed 235 AppStorage.setOrCreate<number>('placeholderIndex', newIndex); 236 if (!this.isMultiPick) { 237 this.currentIndex = newIndex; 238 this.photoUnEdit = this.getCurrentPhoto(); 239 } else { 240 let currentSelectIndex: number = 241 (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ?? 242 Constants.INVALID; 243 this.currentIndex = newIndex; 244 if (currentSelectIndex !== -1) { 245 this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']); 246 this.unSelectEditPhoto(); 247 } 248 249 if (this.totalSelectedCount < this.maxSelectCount) { 250 this.selectStateChangeEdit(); 251 } 252 253 if (this.dataSource.getSelectMode() && this.selectManager) { 254 this.dataSource.setSelectMode(this.selectManager); 255 } 256 this.photoUnEdit = this.getCurrentPhotoInTimeLine(); 257 } 258 this.photoChangedByMediaItem(this.getCurrentPhotoInTimeLine()); 259 } else { // is not in current album or over 500 260 // Search for the position of new image/video after edit in current 500 items failed 261 this.canEdit = false; 262 263 this.editNewUri = uri; 264 this.dataSource.enableGetData(false); 265 this.currentIndex = 0; 266 this.dataSource.getItemIndexByUri( 267 this.editNewUri, 268 (index: number): void => this.onGetItemIndexByNewEditUri(index)); 269 } 270 } 271 } catch (e) { 272 Log.error(TAG, `ON_DATA_RELOADED_WITH_EDIT error ${e}`); 273 } finally { 274 this.appBroadCast.emit(BroadCastConstants.PHOTO_EDIT_SAVE_COMPLETE, []); 275 } 276 277 this.dataSource.onDataReloaded(); 278 } 279 280 photoChangedByMediaItem(mediaItem: MediaItem): void { 281 if (this.dataSource.getSelectMode()) { 282 this.currentIndex = this.dataSource.getDataIndex(mediaItem); 283 } else { 284 this.currentIndex = this.dataSource.getDataIndexByUri(this.photoUnEdit?.uri ?? ''); 285 // 先暂时不更新编辑图片 this.photoUnEdit = mediaItem; 286 } 287 288 let currentPhoto = mediaItem; 289 this.canEdit = AppStorage.get<boolean>(Constants.KEY_OF_IS_THIRD_EDITABLE) as boolean && 290 UiUtil.isEditedEnable(currentPhoto); 291 292 if (currentPhoto === undefined) { 293 Log.error(TAG, 'onPhotoChanged, item is undefined'); 294 } else { 295 this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri) ?? false; 296 this.currentUri = currentPhoto.uri; 297 298 let dataSourceIndex = this.isSelectMode ? 299 (this.selectManager?.getSelectItemDataSourceIndex(currentPhoto) ?? Constants.INVALID) : 300 this.currentIndex; 301 let timelineIndex = this.dataSource.getPositionByIndex(dataSourceIndex); 302 303 if (this.geometryTransitionId !== undefined && this.geometryTransitionId !== '') { 304 AppStorage.setOrCreate<number>('placeholderIndex', timelineIndex as number); 305 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 306 Log.info(TAG, `onPhotoChanged, index: ${this.currentIndex}, currentPhoto: ${currentPhoto.uri}, \ 307 geometryTransitionId = ${this.geometryTransitionId}, placeholderIndex = ${timelineIndex}`); 308 } 309 310 if (this.totalSelectedCount < this.maxSelectCount) { 311 // 根据滑动方向 以及当前的位置 处理 312 this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_PANEL_INDEX, [this.currentUri]); 313 314 this.broadCast.emit(Constants.UPDATE_SELECTED, [true, this.currentUri]); 315 } 316 } 317 } 318 319 getCurrentPhotoInTimeLine(): MediaItem { 320 return this.dataSource.getDataInTimeLine(this.currentIndex)?.data; 321 } 322 323 selectStateChangeEdit(): void { 324 Log.info(TAG, 'change selected.'); 325 let currentPhoto = this.getCurrentPhotoInTimeLine(); 326 if (currentPhoto == undefined) { 327 return; 328 } 329 this.isSelected = true; 330 this.selectManager?.toggleEdit(currentPhoto.uri, true); 331 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 332 333 this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [currentPhoto.uri, this.isSelected]); 334 } 335 336 onGetItemIndexByNewEditUri(index: number): void { 337 Log.info(TAG, `onGetItemIndexByNewEditUri: index=${index}`); 338 if (this.editNewUri.length > 0) { 339 if (index != Constants.NOT_FOUND) { // over 500 340 Log.info(TAG, `data reloaded move to ${index}`); 341 342 AppStorage.setOrCreate<number>('placeholderIndex', index); 343 344 this.dataSource.enableGetData(true); 345 this.dataSource.onDataReloaded(); 346 let result: Results = this.dataSource.getDataInTimeLine(this.currentIndex); 347 if (result !== undefined) { 348 let mediaItem: MediaItem = result.data; 349 let pos: number = result.pos; 350 let thumbnail: string = result.thumbnail; 351 this.currentIndex = pos; 352 this.newMediaItem = mediaItem; 353 this.newMediaItem.setThumbnail(thumbnail); 354 355 this.currentUri = this.editNewUri; 356 this.editNewUri = ''; 357 358 this.updateSelectItemByNewEditIndexFromDataSource(this.currentIndex); 359 360 if (this.dataSource.getSelectMode()) { 361 AppStorage.setOrCreate<number>('placeholderIndex', Constants.INVALID); 362 } 363 } else { 364 this.waitingUpdateIndex = index; 365 this.waitingUpdateData = true; 366 } 367 } else { // other album and can not save new in this album 368 Log.error(TAG, `edit new uri ${this.editNewUri} is invalid`); 369 this.dataSource.enableGetData(true); 370 this.dataSource.onDataReloaded(); 371 372 373 // 此时数据应当使用选中列表的数据 374 let dataImpl: PhotoDataImpl | undefined = 375 BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO) as PhotoDataImpl | undefined; 376 dataImpl?.getDataByUri(this.editNewUri).then((fileAsset: userFileManager.FileAsset | undefined): void => { 377 this.newMediaItem = new MediaItem(fileAsset); 378 this.newMediaItem.setThumbnail(dataImpl?.getThumbnailSafe(this.newMediaItem.uri, this.newMediaItem.path)); 379 380 this.editNewUri = ''; 381 this.updateSelectItemByNewEditItem(this.newMediaItem, this.currentIndex); 382 383 AppStorage.setOrCreate<number>('placeholderIndex', Constants.INVALID); 384 this.geometryTransitionId = ''; 385 }); 386 } 387 } 388 } 389 390 updateSelectItemByNewEditItem(mediaItem: MediaItem, index?: number): void { 391 Log.info(TAG, `updateSelectItemByNewEditItem: index=${index}`); 392 AppStorage.setOrCreate<number>('placeholderIndex', index as number); // timeLineIndex 393 if (!this.isMultiPick) { 394 this.currentIndex = index as number; 395 this.photoUnEdit = this.getCurrentPhoto(); 396 } else { 397 let currentSelectIndex = 398 (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ?? 399 Constants.INVALID; 400 this.currentIndex = index as number; 401 if (currentSelectIndex !== -1) { 402 this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']); 403 this.unSelectEditPhoto(); 404 } 405 406 if (this.totalSelectedCount < this.maxSelectCount) { 407 this.isSelected = true; 408 this.selectManager?.toggleEditThree(mediaItem.uri, true, mediaItem); 409 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 410 this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [mediaItem.uri, this.isSelected]); 411 } 412 413 if (this.dataSource.getSelectMode() && this.selectManager) { 414 this.dataSource.setSelectMode(this.selectManager); 415 this.photoUnEdit = mediaItem; 416 } 417 } 418 this.photoChangedByMediaItem(mediaItem); 419 this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [this.photoUnEdit?.uri ?? '', this.isSelected]); 420 } 421 422 updateSelectItemByNewEditIndexFromDataSource(index: number): void { 423 Log.info(TAG, `updateSelectItemByNewEditIndexFromDataSource: index=${index}`); 424 AppStorage.setOrCreate<number>('placeholderIndex', index); // timeLineIndex 425 if (!this.isMultiPick) { 426 this.currentIndex = index; 427 this.photoUnEdit = this.getCurrentPhoto(); 428 } else { 429 let currentSelectIndex = 430 (this.photoUnEdit ? this.selectManager?.checkItemInSelectMap(this.photoUnEdit) : -1) ?? 431 Constants.INVALID; 432 this.currentIndex = index; 433 if (currentSelectIndex !== -1) { 434 this.broadCast.emit(Constants.UPDATE_SELECTED, [false, this.photoUnEdit?.uri ?? '']); 435 this.unSelectEditPhoto(); 436 } 437 438 if (this.totalSelectedCount < this.maxSelectCount) { 439 this.selectStateChangeEdit(); 440 } 441 442 if (this.dataSource.getSelectMode() && this.selectManager) { 443 this.dataSource.setSelectMode(this.selectManager); 444 } 445 this.photoUnEdit = this.getCurrentPhotoInTimeLine(); 446 } 447 this.photoChangedByMediaItem(this.getCurrentPhotoInTimeLine()); 448 this.broadCast.emit(this.panelId + BroadCastConstants.UPDATE_SELECT, [this.photoUnEdit.uri, this.isSelected]); 449 } 450 451 unSelectEditPhoto(): void { 452 Log.info(TAG, 'unSelectEditPhoto.'); 453 this.selectManager?.toggleEdit(this.photoUnEdit?.uri ?? '', false); 454 this.isSelected = false; 455 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 456 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change`); 457 } 458 459 onMediaLibDataNfy(nfyInfo: MediaObserverNfyInfo): void { 460 Log.info(TAG, `onMediaLibDataNfy nfyInfo: ${JSON.stringify(nfyInfo)}`); 461 this.dataSource.onDataReloaded(); 462 463 if (this.allPhotoDataSource !== undefined && this.allPhotoDataSource !== null) { 464 this.allPhotoDataSource.onDataReloaded(); 465 } 466 } 467 468 onMediaLibDataChange(changeType: string): void { 469 Log.info(TAG, `onMediaLibDataChange type: ${changeType}`); 470 this.dataSource.onDataReloaded(); 471 472 if (this.allPhotoDataSource !== undefined && this.allPhotoDataSource !== null) { 473 this.allPhotoDataSource.onDataReloaded(); 474 } 475 } 476 477 aboutToDisappear(): void { 478 Log.info(TAG, 'call aboutToDisappear'); 479 // 数据清理以及重置 480 if (this.selectManager !== null) { 481 if (this.selectManager?.isPreview) { 482 this.selectManager.isPreview = false; 483 this.selectManager.clickedSet.clear(); 484 485 this.selectManager.previewSet.forEach( 486 (value: string) => { 487 this.selectManager?.clickedSet.add(value); 488 }); 489 } 490 491 this.selectManager?.selectedMap.forEach( 492 (value: MediaItem, key: string) => { 493 if (this.selectManager != null && !(this.selectManager.clickedSet.has(key))) { 494 if (value !== undefined) { 495 this.selectManager.indexMap.delete(value); 496 } 497 this.selectManager.selectedMap.delete(key); 498 } 499 }); 500 501 // selectManager 多余数据处理 502 this.selectManager?.previewSet.clear(); 503 } 504 this.selectManager?.refreshData(); 505 506 this.broadCast.release(); 507 if (this.broadCast) { 508 this.broadCast.off(BrowserConstants.PULL_DOWN_END, this.pullDownFunc); 509 this.broadCast.off(BrowserConstants.DATA_SIZE_CHANGED, this.dataSizeChangedFunc); 510 this.broadCast.off(BroadCastConstants.SELECT, this.selectFunc); 511 this.broadCast.off(BrowserConstants.DATA_CONTENT_CHANGED, this.dataContentChangedFunc); 512 this.broadCast.off(BroadCastConstants.JUMP_THIRD_PHOTO_BROWSER, this.jumpThirdPhotoBrowserFunc); 513 this.broadCast.off(BrowserConstants.SET_DISABLE_SWIPE, this.setDisableSwipeFunc); 514 this.broadCast.off(BroadCastConstants.UPDATE_EDIT_ITEM, this.funcUpdateEditItem); 515 this.broadCast.off(BroadCastConstants.ON_DATA_RELOADED_WITH_EDIT, this.funcOnDataReloadWithEdit); 516 this.broadCast.off(BroadCastConstants.PICKER_PAGE_DISAPPEAR, this.funcPageDisappear); 517 } 518 this.dataSource.release(); 519 mMultimodalInputManager.unregisterListener(); 520 this.controller = undefined; 521 } 522 523 onDataSizeChanged(size: number): void { 524 Log.info(TAG, `onDataSizeChanged, size is ${size}`); 525 if (size == 0) { 526 this.onBackPress(); 527 } 528 } 529 530 setDisableSwipe(value: boolean): void { 531 Log.info(TAG, `set swiper swipe ${value}`); 532 this.canSwipe = value; 533 } 534 535 onPhotoChanged(index: number): void { 536 this.currentIndex = index; 537 let currentPhoto = this.getCurrentPhoto(); 538 this.canEdit = (currentPhoto?.mediaType !== UserFileManagerAccess.MEDIA_TYPE_VIDEO); 539 if (currentPhoto === undefined) { 540 Log.error(TAG, 'onPhotoChanged, item is undefined'); 541 } else { 542 this.isSelected = this.selectManager?.isItemSelected(currentPhoto.uri) ?? false; 543 this.currentUri = currentPhoto.uri; 544 545 let dataSourceIndex = this.isSelectMode ? 546 (this.selectManager?.getSelectItemDataSourceIndex(currentPhoto) ?? Constants.INVALID) : index; 547 let timelineIndex = this.dataSource.getPositionByIndex(dataSourceIndex); 548 AppStorage.setOrCreate<number>('placeholderIndex', timelineIndex); 549 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 550 Log.info(TAG, `onPhotoChanged, index: ${index}, currentPhoto: ${currentPhoto.uri}, \ 551 geometryTransitionId = ${this.geometryTransitionId}, placeholderIndex = ${timelineIndex}`); 552 } 553 } 554 555 selectStateChange() { 556 Log.info(TAG, 'change selected.'); 557 let currentPhoto = this.getCurrentPhoto(); 558 if (currentPhoto == undefined) { 559 return; 560 } 561 this.isSelected = !this.isSelected; 562 if (this.isSelected) { 563 this.selectManager?.toggle(currentPhoto.uri, true); 564 } else { 565 this.selectManager?.toggle(currentPhoto.uri, false); 566 } 567 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 568 this.geometryTransitionId = this.browserController.pageFrom + currentPhoto.getHashCode() + this.isSelected; 569 this.broadCast.emit(BroadCastConstants.UPDATE_SELECT, [currentPhoto.uri, this.isSelected]); 570 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change geometryTransitionId ${this.geometryTransitionId}`); 571 } 572 573 selectEditPhoto(newIdIndex: number) { 574 this.currentIndex = newIdIndex; 575 this.dataSource.resetSelectMode(); 576 let currentPhoto = this.getCurrentPhoto(); 577 if (currentPhoto == undefined) { 578 return; 579 } 580 581 this.isSelected = false; 582 583 this.selectManager?.toggle(this.photoUnEdit?.uri ?? '', false); 584 585 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 586 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after state change`); 587 } 588 589 selectCallback(position: number, key: string, value: boolean) { 590 if (key === this.currentUri) { 591 this.isSelected = value; 592 } 593 if (this.selectManager) { 594 this.selectManager.toggle(key, value); 595 } 596 this.totalSelectedCount = this.selectManager?.getSelectedCount() ?? 0; 597 Log.info(TAG, `totalSelectedCount: ${this.totalSelectedCount} after select callback`); 598 } 599 600 onPageChanged() { 601 if (this.pageStatus) { 602 this.onPageShow(); 603 } else { 604 this.onPageHide(); 605 } 606 } 607 608 onPageShow() { 609 Log.debug(TAG, 'onPageShow'); 610 this.appBroadCast.emit(BroadCastConstants.THIRD_ROUTE_PAGE, []); 611 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [true, this.mTransition]); 612 } 613 614 onPageHide() { 615 Log.debug(TAG, 'onPageHide'); 616 this.appBroadCast.emit(BroadCastConstants.PHOTO_BROWSER_ACTIVE, [false, this.mTransition]); 617 // 数据清理以及重置 618 619 if (this.selectManager !== null) { 620 if (this.selectManager?.isPreview === true) { 621 this.selectManager.isPreview = false; 622 this.selectManager.clickedSet.clear(); 623 for (let item of this.selectManager.previewSet) { 624 this.selectManager?.clickedSet.add(item); 625 } 626 } 627 628 this.selectManager?.selectedMap.forEach( 629 (value: MediaItem, key: string) => { 630 if (this.selectManager != null && !(this.selectManager.clickedSet.has(key))) { 631 if (value !== undefined) { 632 this.selectManager.indexMap.delete(value); 633 } 634 this.selectManager.selectedMap.delete(key); 635 } 636 }); 637 // selectManager 多余数据处理 638 this.selectManager?.previewSet.clear(); 639 this.selectManager?.refreshData(); 640 } 641 } 642 643 onMenuClicked(action: Action) { 644 Log.info(TAG, `onMenuClicked, action: ${action.actionID}`); 645 if (action.actionID === Action.BACK.actionID) { 646 interface Msg { 647 from: string; 648 } 649 let msg: Msg = { 650 from: BigDataConstants.BY_CLICK, 651 } 652 ReportToBigDataUtil.report(BigDataConstants.ESC_PHOTO_BROWSER_WAY, msg); 653 this.onBackPress(); 654 } else if (action.actionID === Action.MATERIAL_SELECT.actionID) { 655 Log.info(TAG, 'click UN_SELECTED'); 656 this.selectStateChange(); 657 } else if (action.actionID === Action.SELECTED.actionID) { 658 Log.info(TAG, 'click SELECTED'); 659 this.selectStateChange(); 660 } else if (action.actionID === Action.OK.actionID) { 661 Log.info(TAG, 'click OK'); 662 this.setPickResult(); 663 } else if (action.actionID === Action.EDIT.actionID) { 664 Log.info(TAG, 'click EDIT'); 665 let currentPhoto = this.getCurrentPhoto(); 666 if (currentPhoto == undefined || currentPhoto.size == 0) { 667 Log.warn(TAG, 'currentPhoto is undefined or size is 0.'); 668 return; 669 } 670 AppStorage.setOrCreate<MediaItem | undefined>('EditorMediaItem', currentPhoto); 671 AppStorage.setOrCreate<string>('EditorAlbumUri', this.dataSource.getAlbumDataSource()?.albumUri); 672 router.pushUrl({ 673 url: 'pages/EditMain' 674 }) 675 this.isToEdit = true; 676 } 677 } 678 679 getCurrentPhoto(): MediaItem { 680 Log.debug(TAG, 'getCurrentPhoto ' + this.currentIndex); 681 return this.dataSource.getData(this.currentIndex)?.data; 682 } 683 684 onBackPress() { 685 if (!this.isPageDisappear) { 686 this.selectManager?.refreshData(); 687 } 688 if (this.geometryTransitionEnable) { 689 this.controller?.finishAnimation((): void => this.onBackPressInner()); 690 } else { 691 router.back({ 692 url: '', 693 params: { index: this.currentIndex } 694 }); 695 } 696 return true; 697 } 698 699 @Builder 700 buildCheckBox() { 701 if (this.isMultiPick) { 702 Row() { 703 Image(this.isSelected ? $r('app.media.picker_checkbox_selected_dark') : $r('app.media.picker_checkbox_unselected_dark')) 704 .width($r('app.float.icon_size')) 705 .aspectRatio(1) 706 .key('Checkbox_' + this.currentIndex) 707 .margin({ 708 right: $r('sys.float.ohos_id_max_padding_end'), 709 bottom: $r('app.float.picker_browser_checkbox_margin_bottom') 710 }) 711 .onClick(() => { 712 this.selectStateChange(); 713 }) 714 } 715 .justifyContent(FlexAlign.End) 716 .width('100%') 717 .visibility(this.isShowBar ? Visibility.Visible : Visibility.Hidden) 718 .opacity(this.geometryOpacity) 719 .transition(TransitionEffect.opacity(0)) 720 .hitTestBehavior(HitTestMode.Transparent) 721 } 722 } 723 724 @Builder 725 buildPanel() { 726 ThirdSelectedPanel({ 727 maxSelectCount: this.maxSelectCount, 728 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 729 isBrowserMode: true, 730 isMultiPick: this.isMultiPick, 731 mTransition: TAG, 732 isFromFa: this.isFromFa, 733 currentUri: this.currentUri, 734 isShowBar: $isShowBar, 735 totalSelectedCount: $totalSelectedCount 736 }) 737 .opacity(this.geometryOpacity) 738 .transition(TransitionEffect.opacity(0)) 739 .hitTestBehavior(HitTestMode.Transparent) 740 } 741 742 build() { 743 Stack({ alignContent: Alignment.Bottom }) { 744 Stack({ alignContent: Alignment.TopStart }) { 745 PhotoBrowserBg({ isShowBar: $isShowBar }) 746 .opacity(this.geometryOpacity) 747 .transition(TransitionEffect.opacity(0)) 748 749 PhotoSwiper({ 750 dataSource: this.dataSource, 751 mTransition: this.mTransition, 752 onPhotoChanged: (index: number) => this.onPhotoChanged(index), 753 swiperController: this.controller, 754 verifyPhotoScaledFunc: (matrix?: Matrix4.Matrix4Transit) => this.verifyPhotoScaled(matrix), 755 geometryTransitionEnable: true, 756 broadCast: $broadCast, 757 isRunningAnimation: $isRunningAnimation, 758 isInSelectedMode: true 759 }) 760 761 if (this.isHorizontal) { 762 MouseTurnPageOperation({ 763 dataSource: this.dataSource, 764 controller: this.controller, 765 isPhotoScaled: this.isPhotoScaled, 766 isShowBar: this.isShowBar 767 }) 768 .opacity(this.geometryOpacity) 769 .transition(TransitionEffect.opacity(0)) 770 .hitTestBehavior(HitTestMode.Transparent) 771 772 } 773 ThirdSelectPhotoBrowserActionBar({ 774 isMultiPick: this.isMultiPick, 775 onMenuClicked: (action: Action): void => this.onMenuClicked(action), 776 title: this.title, 777 isThird: true, 778 isShowBar: $isShowBar, 779 totalSelectedCount: $totalSelectedCount 780 }) 781 .opacity(this.geometryOpacity) 782 .transition(TransitionEffect.opacity(0)) 783 .hitTestBehavior(HitTestMode.Transparent) 784 } 785 786 this.buildCheckBox() 787 this.buildPanel() 788 } 789 .padding({ bottom: this.leftBlank[3] }) 790 } 791 792 pageTransition() { 793 PageTransitionEnter({ type: RouteType.None, duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION }) 794 .opacity(0) 795 PageTransitionExit({ duration: BrowserConstants.PAGE_SHOW_ANIMATION_DURATION }) 796 .opacity(0) 797 } 798 799 verifyPhotoScaled(matrix?: Matrix4.Matrix4Transit): void { 800 if (matrix) { 801 let mat: number[] | undefined = (matrix.copy() as Matrix4x4).matrix4x4; 802 if (mat) { 803 let xScale: number = mat[0]; 804 let yScale: number = mat[5]; 805 Log.info(TAG, `photo in PhotoItem has Scaled x scale: ${xScale}, y scale: ${yScale}, mat: ${mat}`); 806 this.isPhotoScaled = xScale != 1 || yScale != 1 807 } 808 } else { 809 this.isPhotoScaled = false 810 Log.info(TAG, `photo in PhotoItem has not Scaled isPhotoScaled: ${this.isPhotoScaled}`); 811 } 812 } 813 814 private onBackPressInner(): void { 815 this.browserController.hideBrowser(); 816 } 817 818 private jumpBrowserCallback(name: string, item: MediaItem, isSelectMode = false): void { 819 if (this.dataSource.getSelectMode() === false) { 820 if (this.selectManager) { 821 this.dataSource.setSelectMode(this.selectManager); 822 } 823 this.currentIndex = this.dataSource.getDataIndex(item); 824 this.onPhotoChanged(this.currentIndex); 825 this.dataSource.onDataReloaded(); 826 } else { 827 if (this.dataSource && item && this.currentUri != item.uri) { 828 Log.debug(TAG, `jumpBrowserCallback jump to item.uri ${item.uri}, currentUri ${this.currentUri}`) 829 let tgtIndex = this.dataSource.getDataIndex(item); 830 Log.debug(TAG, `jump to index ${tgtIndex}`); 831 this.onPhotoChanged(tgtIndex); 832 this.dataSource.onDataReloaded(); 833 } 834 } 835 this.photoUnEdit = item; 836 } 837 838 private setPickResult(): void { 839 if (this.isFromFa) { 840 let currentPhoto = this.getCurrentPhoto(); 841 if (currentPhoto) { 842 Log.debug(TAG, `setPickResult. updateFormData obj: ${currentPhoto.uri} currentIndex: ${this.currentIndex}`); 843 this.appBroadCast.emit(BroadCastConstants.SAVE_FORM_EDITOR_DATA, 844 ['', AppStorage.get<string>(FormConstants.FORM_ITEM_ALBUM_URI), AppStorage.get<Resource>(FormConstants.FORM_ITEM_DISPLAY_NAME), 845 currentPhoto.uri, false]); 846 } else { 847 Log.error(TAG, 'Fa setPickResult is null'); 848 } 849 return; 850 } 851 let uriArray: string[] = []; 852 let mediaType: number; 853 if (this.isMultiPick) { 854 if (this.selectManager === null) { 855 Log.error(TAG, 'Select Manager empty'); 856 return; 857 } 858 if (this.selectManager.isPreview) { 859 uriArray = SelectUtil.getUriArray(this.selectManager?.previewSet ?? new Set()); 860 } else { 861 uriArray = SelectUtil.getUriArray(this.selectManager?.clickedSet ?? new Set()); 862 } 863 Log.info(TAG, `uri size: ${uriArray}`); 864 } else { 865 if (!AppStorage.get('isReplace')) { 866 this.currentIndex = 0; 867 } 868 let currentPhoto = this.getCurrentPhoto(); 869 if (currentPhoto == undefined) { 870 return; 871 } 872 uriArray = [currentPhoto.uri]; 873 } 874 let promise: Promise<void> = SelectUtil.grantPermissionForUris(uriArray, this.bundleName); 875 let abilityResult: ability.AbilityResult = { 876 resultCode: 0, 877 want: { 878 parameters: { 879 'select-item-list': uriArray, 880 } 881 } 882 }; 883 let localStorage = LocalStorage.getShared(); 884 if (localStorage?.has(Constants.PHOTO_PICKER_SESSION_KEY)) { 885 let session = localStorage?.get<UIExtensionContentSession>(Constants.PHOTO_PICKER_SESSION_KEY); 886 let param = localStorage?.get<SelectParams>(Constants.PHOTO_PICKER_PARAMS_KEY); 887 if (uriArray === null || uriArray === undefined || uriArray?.length === 0) { 888 session?.terminateSelfWithResult(abilityResult).then((result: void) => { 889 Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`); 890 }); 891 } else { 892 try { 893 if (param?.bundleName) { 894 Log.debug(TAG, `grantUriPermission to ${param?.bundleName}`); 895 uriArray.forEach(uri => { 896 fileShare.grantUriPermission(uri, param?.bundleName, 897 wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION, 898 (err: BusinessError): void => { 899 Log.error(TAG, `failed to grantUriPermission to ${param?.bundleName}`); 900 session?.terminateSelfWithResult(abilityResult).then((result: void) => { 901 Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`); 902 }); 903 }); 904 }) 905 } 906 } catch (err) { 907 Log.error(TAG, `err: ${JSON.stringify(err)}`); 908 session?.terminateSelfWithResult(abilityResult).then((result: void) => { 909 Log.info(TAG, `session terminateSelfWithResult abilityResult: ${JSON.stringify(abilityResult)} result: ${result}`); 910 }); 911 } 912 } 913 } else { 914 let context: common.UIAbilityContext = AppStorage.get<common.UIAbilityContext>('photosAbilityContext') as common.UIAbilityContext; 915 context.terminateSelfWithResult(abilityResult).then((result: void) => { 916 Log.info(TAG, `terminateSelf result: ${result}, self result ${JSON.stringify(abilityResult)}`); 917 }); 918 } 919 let selectedMap: Map<string, MediaItem> = this.selectManager?.selectedMap ?? new Map(); 920 SelectUtil.getCountOfMedia(uriArray, selectedMap).then((result: number[]) => { 921 let isOrigin: boolean = AppStorage.get<boolean>(THIRD_SELECT_IS_ORIGIN) ?? false; 922 if (isOrigin == undefined) { 923 isOrigin = false; 924 } 925 926 interface Msg { 927 isOriginalChecked: boolean; 928 selectItemSize: number; 929 selectImageSize: number; 930 selectVideoSize: number; 931 } 932 933 let msg: Msg = { 934 isOriginalChecked: isOrigin, 935 selectItemSize: (uriArray === null || uriArray === undefined || uriArray.length <= 0) ? 0 : uriArray.length, 936 selectImageSize: this.isMultiPick ? result[0] : (mediaType === UserFileManagerAccess.MEDIA_TYPE_IMAGE ? 1 : 0), 937 selectVideoSize: this.isMultiPick ? result[1] : (mediaType === UserFileManagerAccess.MEDIA_TYPE_VIDEO ? 1 : 0) 938 } 939 ReportToBigDataUtil.report(BigDataConstants.SELECT_PICKER_RESULT, msg); 940 }); 941 } 942} 943 944/** 945 * 用于预览已选中的图片的dataSource 946 * 数据源取自selectManager的当前已选中图片 947 */ 948class ThirdBrowserDataSource extends PhotoDataSource { 949 private isSelectMode = false; 950 private selectedItems: MediaItem[] = []; 951 952 totalCount() { 953 if (this.isSelectMode) { 954 return this.selectedItems.length; 955 } 956 return super.totalCount(); 957 } 958 959 getData(index: number): Results { 960 if (this.isSelectMode) { 961 return this.packData(index, this.selectedItems[index]) as Results; 962 } 963 return super.getData(index) as Results; 964 } 965 966 setSelectMode(manager: ThirdSelectManager) { 967 this.isSelectMode = true; 968 this.selectedItems = manager.getSelectItems(); 969 } 970 971 getDataIndex(item: MediaItem): number { 972 if (this.isSelectMode) { 973 for (let i = 0; i < this.selectedItems.length; i++) { 974 let clicked: MediaItem = this.selectedItems[i]; 975 if (clicked.uri === item.uri) { 976 return i; 977 } 978 } 979 return Constants.NOT_FOUND; 980 } 981 return super.getDataIndex(item); 982 } 983 984 getDataInTimeLine(index: number): Results { 985 return super.getData(index) as Results; 986 } 987 988 getSelectMode(): boolean { 989 return this.isSelectMode; 990 } 991 992 resetSelectMode() { 993 this.isSelectMode = false; 994 } 995} 996