1/* 2 * Copyright (c) 2024 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 photoAccessHelper from '@ohos.file.photoAccessHelper'; 17const FILTER_MEDIA_TYPE_ALL = 'FILTER_MEDIA_TYPE_ALL'; 18const FILTER_MEDIA_TYPE_IMAGE = 'FILTER_MEDIA_TYPE_IMAGE'; 19const FILTER_MEDIA_TYPE_VIDEO = 'FILTER_MEDIA_TYPE_VIDEO'; 20 21@Component 22export struct PhotoPickerComponent { 23 pickerOptions?: PickerOptions | undefined; 24 onSelect?: (uri: string) => void; 25 onDeselect?: (uri: string) => void; 26 onItemClicked?: (itemInfo: ItemInfo, clickType: ClickType) => boolean; 27 onEnterPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean; 28 onExitPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean; 29 onPickerControllerReady?: () => void; 30 onPhotoBrowserChanged?: (browserItemInfo: BaseItemInfo) => boolean; 31 onSelectedItemsDeleted?: ItemsDeletedCallback; 32 onExceedMaxSelected?: ExceedMaxSelectedCallback; 33 onCurrentAlbumDeleted?: CurrentAlbumDeletedCallback; 34 onVideoPlayStateChanged?: VideoPlayStateChangedCallback; 35 @ObjectLink @Watch('onChanged') pickerController: PickerController; 36 private proxy: UIExtensionProxy | undefined; 37 38 private onChanged(): void { 39 if (!this.proxy) { 40 return; 41 } 42 let data = this.pickerController?.data; 43 if (data?.has('SET_SELECTED_URIS')) { 44 this.proxy.send({'selectUris': data?.get('SET_SELECTED_URIS') as Array<string>}); 45 console.info('PhotoPickerComponent onChanged: SET_SELECTED_URIS'); 46 } else if (data?.has('SET_ALBUM_URI')) { 47 this.proxy.send({'albumUri': data?.get('SET_ALBUM_URI') as string}); 48 console.info('PhotoPickerComponent onChanged: SET_ALBUM_URI'); 49 } else if (data?.has('SET_MAX_SELECT_COUNT')) { 50 this.onSetMaxSelectCount(data); 51 } else if (data?.has('SET_PHOTO_BROWSER_ITEM')) { 52 this.onSetPhotoBrowserItem(data); 53 } else if (data?.has('EXIT_PHOTO_BROWSER')) { 54 this.handleExitPhotoBrowser(); 55 } else if (data?.has('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY')) { 56 this.onSetPhotoBrowserUIElementVisibility(data); 57 } else { 58 console.info('PhotoPickerComponent onChanged: other case'); 59 } 60 } 61 62 private onSetMaxSelectCount(data?: Map<string, Object>): void { 63 let maxSelected: MaxSelected = data?.get('SET_MAX_SELECT_COUNT') as MaxSelected; 64 let map: Map<MaxCountType, number> | undefined = maxSelected?.data; 65 this.proxy.send({ 66 'totalCount': map?.get(MaxCountType.TOTAL_MAX_COUNT), 67 'photoCount': map?.get(MaxCountType.PHOTO_MAX_COUNT), 68 'videoCount': map?.get(MaxCountType.VIDEO_MAX_COUNT) 69 }); 70 console.info('PhotoPickerComponent onChanged: SET_MAX_SELECT_COUNT'); 71 } 72 73 private onSetPhotoBrowserItem(data?: Map<string, Object>): void { 74 let photoBrowserRangeInfo: PhotoBrowserRangeInfo = data?.get('SET_PHOTO_BROWSER_ITEM') as PhotoBrowserRangeInfo; 75 this.proxy?.send({ 76 'itemUri': photoBrowserRangeInfo?.uri, 77 'photoBrowserRange': photoBrowserRangeInfo?.photoBrowserRange 78 }); 79 console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_ITEM'); 80 } 81 82 private handleExitPhotoBrowser(): void { 83 this.proxy.send({'exitPhotoBrowser': true}); 84 console.info('PhotoPickerComponent onChanged: EXIT_PHOTO_BROWSER'); 85 } 86 87 private onSetPhotoBrowserUIElementVisibility(data?: Map<string, Object>): void { 88 let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility = 89 data?.get('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY') as PhotoBrowserUIElementVisibility; 90 this.proxy?.send({ 91 'elements': photoBrowserUIElementVisibility?.elements, 92 'isVisible': photoBrowserUIElementVisibility?.isVisible 93 }); 94 console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY'); 95 } 96 97 build() { 98 Row() { 99 Column() { 100 SecurityUIExtensionComponent({ 101 parameters: { 102 "ability.want.params.uiExtensionTargetType":"photoPicker", 103 uri: "multipleselect", 104 targetPage: "photoPage", 105 filterMediaType: this.convertMIMETypeToFilterType(this.pickerOptions?.MIMEType), 106 maxSelectNumber: this.pickerOptions?.maxSelectNumber as number, 107 isPhotoTakingSupported: this.pickerOptions?.isPhotoTakingSupported as boolean, 108 isEditSupported: false, 109 recommendationOptions: this.pickerOptions?.recommendationOptions as photoAccessHelper.RecommendationOptions, 110 preselectedUri: this.pickerOptions?.preselectedUris as Array<string>, 111 isFromPickerView: true, 112 isNeedActionBar: false, 113 isNeedSelectBar: false, 114 isSearchSupported: this.pickerOptions?.isSearchSupported as boolean, 115 checkBoxColor: this.pickerOptions?.checkBoxColor as string, 116 backgroundColor: this.pickerOptions?.backgroundColor as string, 117 checkboxTextColor: this.pickerOptions?.checkboxTextColor as string, 118 photoBrowserBackgroundColorMode: this.pickerOptions?.photoBrowserBackgroundColorMode as PickerColorMode, 119 isRepeatSelectSupported: this.pickerOptions?.isRepeatSelectSupported as boolean, 120 maxSelectedReminderMode: this.pickerOptions?.maxSelectedReminderMode as ReminderMode, 121 orientation: this.pickerOptions?.orientation as PickerOrientation, 122 selectMode: this.pickerOptions?.selectMode as SelectMode, 123 maxPhotoSelectNumber: this.pickerOptions?.maxPhotoSelectNumber as number, 124 maxVideoSelectNumber: this.pickerOptions?.maxVideoSelectNumber as number, 125 isOnItemClickedSet: this.onItemClicked? true : false, 126 isPreviewForSingleSelectionSupported: this.pickerOptions?.isPreviewForSingleSelectionSupported as boolean, 127 isSlidingSelectionSupported: this.pickerOptions?.isSlidingSelectionSupported as boolean, 128 photoBrowserCheckboxPosition: this.pickerOptions?.photoBrowserCheckboxPosition as [number, number], 129 gridMargin: this.pickerOptions?.gridMargin as Margin, 130 photoBrowserMargin: this.pickerOptions?.photoBrowserMargin as Margin 131 } 132 }).height('100%').width('100%').onRemoteReady((proxy) => { 133 this.proxy = proxy; 134 console.info('PhotoPickerComponent onRemoteReady'); 135 }).onReceive((data) => { 136 let wantParam: Record<string, Object> = data as Record<string, Object>; 137 this.handleOnReceive(wantParam); 138 }).onError(() => { 139 console.info('PhotoPickerComponent onError'); 140 }); 141 } 142 .width('100%') 143 } 144 .height('100%') 145 } 146 147 private handleOnReceive(wantParam: Record<string, Object>): void { 148 let dataType = wantParam['dataType'] as string; 149 console.info('PhotoPickerComponent onReceive: dataType = ' + dataType); 150 if (dataType === 'selectOrDeselect') { 151 this.handleSelectOrDeselect(wantParam); 152 } else if (dataType === 'itemClick') { 153 this.handleItemClick(wantParam); 154 } else if (dataType === 'onPhotoBrowserStateChanged') { 155 this.handleEnterOrExitPhotoBrowser(wantParam); 156 } else if (dataType === 'remoteReady') { 157 if (this.onPickerControllerReady) { 158 this.onPickerControllerReady(); 159 console.info('PhotoPickerComponent onReceive: onPickerControllerReady'); 160 } 161 } else if (dataType === 'onPhotoBrowserChanged') { 162 this.handlePhotoBrowserChange(wantParam); 163 } else if (dataType === 'onVideoPlayStateChanged') { 164 this.handleVideoPlayStateChange(wantParam) 165 } else { 166 this.handleOtherOnReceive(wantParam); 167 console.info('PhotoPickerComponent onReceive: other case'); 168 } 169 console.info('PhotoPickerComponent onReceive' + JSON.stringify(wantParam)); 170 } 171 172 private handleOtherOnReceive(wantParam: Record<string, Object>): void{ 173 let dataType = wantParam.dataType as string; 174 if (dataType === 'exceedMaxSelected') { 175 if (this.onExceedMaxSelected) { 176 this.onExceedMaxSelected(wantParam.maxCountType as MaxCountType); 177 } 178 } else if (dataType === 'selectedItemsDeleted') { 179 if (this.onSelectedItemsDeleted) { 180 this.onSelectedItemsDeleted(wantParam.selectedItemInfos as Array<BaseItemInfo>); 181 } 182 } else if (dataType === 'currentAlbumDeleted') { 183 if (this.onCurrentAlbumDeleted) { 184 this.onCurrentAlbumDeleted(); 185 } 186 } else { 187 console.info('PhotoPickerComponent onReceive: other case'); 188 } 189 } 190 191 private handleSelectOrDeselect(wantParam: Record<string, Object>): void { 192 let isSelect: boolean = wantParam['isSelect'] as boolean; 193 if (isSelect) { 194 if (this.onSelect) { 195 this.onSelect(wantParam['select-item-list'] as string); 196 console.info('PhotoPickerComponent onReceive: onSelect'); 197 } 198 } else { 199 if (this.onDeselect) { 200 this.onDeselect(wantParam['select-item-list'] as string); 201 console.info('PhotoPickerComponent onReceive: onDeselect'); 202 } 203 } 204 } 205 206 private handleItemClick(wantParam: Record<string, Object>): void { 207 if (this.onItemClicked) { 208 let clickType: ClickType = ClickType.SELECTED; 209 let type = wantParam['clickType'] as string; 210 if (type === 'select') { 211 clickType = ClickType.SELECTED; 212 } else if (type === 'deselect') { 213 clickType = ClickType.DESELECTED; 214 } else { 215 console.info('PhotoPickerComponent onReceive: other clickType'); 216 } 217 let itemInfo: ItemInfo = new ItemInfo(); 218 let itemType: string = wantParam['itemType'] as string; 219 if (itemType === 'thumbnail') { 220 itemInfo.itemType = ItemType.THUMBNAIL; 221 } else if (itemType === 'camera') { 222 itemInfo.itemType = ItemType.CAMERA; 223 } else { 224 console.info('PhotoPickerComponent onReceive: other itemType'); 225 } 226 itemInfo.uri = wantParam['uri'] as string; 227 itemInfo.mimeType = wantParam['mimeType'] as string; 228 itemInfo.width = wantParam['width'] as number; 229 itemInfo.height = wantParam['height'] as number; 230 itemInfo.size = wantParam['size'] as number; 231 itemInfo.duration = wantParam['duration'] as number; 232 let result: boolean = this.onItemClicked(itemInfo, clickType); 233 console.info('PhotoPickerComponent onReceive: onItemClicked = ' + clickType); 234 if (this.proxy) { 235 if (itemType === 'thumbnail' && clickType === ClickType.SELECTED) { 236 this.proxy.send({'clickConfirm': itemInfo.uri, 'isConfirm': result}); 237 console.info('PhotoPickerComponent onReceive: click confirm: uri = ' + itemInfo.uri + 'isConfirm = ' + result); 238 } 239 if (itemType === 'camera') { 240 this.proxy.send({'enterCamera': result}); 241 console.info('PhotoPickerComponent onReceive: enter camera ' + result); 242 } 243 } 244 } 245 } 246 247 private handleEnterOrExitPhotoBrowser(wantParam: Record<string, Object>): void { 248 let isEnter: boolean = wantParam['isEnter'] as boolean; 249 let photoBrowserInfo: PhotoBrowserInfo = new PhotoBrowserInfo(); 250 photoBrowserInfo.animatorParams = new AnimatorParams(); 251 photoBrowserInfo.animatorParams.duration = wantParam['duration'] as number; 252 photoBrowserInfo.animatorParams.curve = wantParam['curve'] as Curve | ICurve | string; 253 if (isEnter) { 254 if (this.onEnterPhotoBrowser) { 255 this.onEnterPhotoBrowser(photoBrowserInfo); 256 } 257 } else { 258 if (this.onExitPhotoBrowser) { 259 this.onExitPhotoBrowser(photoBrowserInfo); 260 } 261 } 262 console.info('PhotoPickerComponent onReceive: onPhotoBrowserStateChanged = ' + isEnter); 263 } 264 265 private handlePhotoBrowserChange(wantParam: Record<string, Object>): void { 266 let browserItemInfo: BaseItemInfo = new BaseItemInfo(); 267 browserItemInfo.uri = wantParam['uri'] as string; 268 if (this.onPhotoBrowserChanged) { 269 this.onPhotoBrowserChanged(browserItemInfo); 270 } 271 console.info('PhotoPickerComponent onReceive: onPhotoBrowserChanged = ' + browserItemInfo.uri); 272 } 273 274 private handleVideoPlayStateChange(wantParam: Record<string, Object>): void { 275 if(this.onVideoPlayStateChanged) { 276 this.onVideoPlayStateChanged(wantParam.state as VideoPlayerState) 277 } 278 console.info('PhotoPickerComponent onReceive: onVideoPlayStateChanged = ' + JSON.stringify(wantParam)); 279 } 280 281 private convertMIMETypeToFilterType(mimeType: photoAccessHelper.PhotoViewMIMETypes): string { 282 let filterType: string; 283 if (mimeType === photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE) { 284 filterType = FILTER_MEDIA_TYPE_IMAGE; 285 } else if (mimeType === photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE) { 286 filterType = FILTER_MEDIA_TYPE_VIDEO; 287 } else { 288 filterType = FILTER_MEDIA_TYPE_ALL; 289 } 290 console.info('PhotoPickerComponent convertMIMETypeToFilterType' + JSON.stringify(filterType)); 291 return filterType; 292 } 293} 294 295export type ItemsDeletedCallback = (baseItemInfos: Array<BaseItemInfo>) => void; 296 297export type ExceedMaxSelectedCallback = (exceedMaxCountType: MaxCountType) => void; 298 299export type CurrentAlbumDeletedCallback = () => void; 300 301export type VideoPlayStateChanged = (state: VideoPlayerState) => {} void; 302 303@Observed 304export class PickerController { 305 data?: Map<string, Object>; 306 307 setData(type: DataType, data: Object) { 308 if (data === undefined) { 309 return; 310 } 311 if (type === DataType.SET_SELECTED_URIS) { 312 if (data instanceof Array) { 313 let uriLists: Array<string> = data as Array<string>; 314 if (uriLists) { 315 this.data = new Map([['SET_SELECTED_URIS', [...uriLists]]]); 316 console.info('PhotoPickerComponent SET_SELECTED_URIS' + JSON.stringify(uriLists)); 317 } 318 } 319 } else if (type === DataType.SET_ALBUM_URI) { 320 let albumUri: string = data as string; 321 if (albumUri !== undefined) { 322 this.data = new Map([['SET_ALBUM_URI', albumUri]]); 323 console.info('PhotoPickerComponent SET_ALBUM_URI' + JSON.stringify(albumUri)); 324 } 325 } else { 326 console.info('PhotoPickerComponent setData: other case'); 327 } 328 } 329 330 setMaxSelected(maxSelected: MaxSelected) { 331 if (maxSelected) { 332 this.data = new Map([['SET_MAX_SELECT_COUNT', maxSelected]]); 333 console.info('PhotoPickerComponent SET_MAX_SELECT_COUNT' + JSON.stringify(maxSelected)); 334 } 335 } 336 337 setPhotoBrowserItem(uri: string, photoBrowserRange?: PhotoBrowserRange) { 338 let photoBrowserRangeInfo: PhotoBrowserRangeInfo = new PhotoBrowserRangeInfo(); 339 photoBrowserRangeInfo.uri = uri; 340 let newPhotoBrowserRange = photoBrowserRange ? photoBrowserRange : PhotoBrowserRange.ALL; 341 photoBrowserRangeInfo.photoBrowserRange = newPhotoBrowserRange; 342 this.data = new Map([['SET_PHOTO_BROWSER_ITEM', photoBrowserRangeInfo]]); 343 console.info('PhotoPickerComponent SET_PHOTO_BROWSER_ITEM' + JSON.stringify(photoBrowserRangeInfo)); 344 } 345 346 exitPhotoBrowser() { 347 this.data = new Map([['EXIT_PHOTO_BROWSER', true]]); 348 console.info('PhotoPickerComponent EXIT_PHOTO_BROWSER'); 349 } 350 351 setPhotoBrowserUIElementVisibility(elements: Array<PhotoBrowserUIElement>, isVisible?: boolean) { 352 let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility = new PhotoBrowserUIElementVisibility(); 353 photoBrowserUIElementVisibility.elements = elements; 354 photoBrowserUIElementVisibility.isVisible = isVisible; 355 this.data = new Map([['SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY', photoBrowserUIElementVisibility]]); 356 console.info('PhotoPickerComponent SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY ' + 357 JSON.stringify(photoBrowserUIElementVisibility)); 358 } 359} 360 361export class PickerOptions extends photoAccessHelper.BaseSelectOptions { 362 checkBoxColor?: string; 363 backgroundColor?: string; 364 isRepeatSelectSupported?: boolean; 365 checkboxTextColor?: string; 366 photoBrowserBackgroundColorMode?: PickerColorMode; 367 maxSelectedReminderMode?: ReminderMode; 368 orientation?: PickerOrientation; 369 selectMode?: SelectMode; 370 maxPhotoSelectNumber?: number; 371 maxVideoSelectNumber?: number; 372 isSlidingSelectionSupported?: boolean; 373 photoBrowserCheckboxPosition?: [number, number]; 374 gridMargin?: Margin; 375 photoBrowserMargin?: Margin; 376} 377 378export class BaseItemInfo { 379 uri?: string; 380 mimeType?: string; 381 width?: number; 382 height?: number; 383 size?: number; 384 duration?: number; 385} 386 387export class ItemInfo extends BaseItemInfo { 388 itemType?: ItemType; 389} 390 391export class PhotoBrowserInfo { 392 animatorParams?: AnimatorParams; 393} 394 395export class AnimatorParams { 396 duration?: number; 397 curve?: Curve | ICurve | string; 398} 399 400export class MaxSelected { 401 data?: Map<MaxCountType, number>; 402} 403 404class PhotoBrowserRangeInfo { 405 uri?: string; 406 photoBrowserRange?: PhotoBrowserRange; 407} 408 409class PhotoBrowserUIElementVisibility { 410 elements?: Array<PhotoBrowserUIElement>; 411 isVisible?: boolean; 412} 413 414export enum DataType { 415 SET_SELECTED_URIS = 1, 416 SET_ALBUM_URI = 2 417} 418 419export enum ItemType { 420 THUMBNAIL = 0, 421 CAMERA = 1 422} 423 424export enum ClickType { 425 SELECTED = 0, 426 DESELECTED = 1 427} 428 429export enum PickerOrientation { 430 VERTICAL = 0, 431 HORIZONTAL = 1 432} 433 434export enum SelectMode { 435 SINGLE_SELECT = 0, 436 MULTI_SELECT = 1 437} 438 439export enum PickerColorMode { 440 AUTO = 0, 441 LIGHT = 1, 442 DARK = 2 443} 444 445export enum ReminderMode { 446 NONE = 0, 447 TOAST = 1, 448 MASK = 2 449} 450 451export enum MaxCountType { 452 TOTAL_MAX_COUNT = 0, 453 PHOTO_MAX_COUNT = 1, 454 VIDEO_MAX_COUNT = 2 455} 456 457export enum PhotoBrowserRange { 458 ALL = 0, 459 SELECTED_ONLY = 1 460} 461 462export enum PhotoBrowserUIElement { 463 CHECKBOX = 0, 464 BACK_BUTTON = 1 465} 466 467export enum VideoPlayerState { 468 PLAYING = 0, 469 PAUSED = 1, 470 STOPPED = 3, 471 SEEK_START = 4, 472 SEEK_FINISH = 5 473}