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