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'; 17import { fileIo as fs, fileUri } from '@kit.CoreFileKit'; 18import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit'; 19import { bundleManager } from '@kit.AbilityKit'; 20import { LengthMetrics, LengthUnit } from '@kit.ArkUI'; 21 22const PHOTO_VIEW_MIME_TYPE_MAP = new Map([ 23 ['*/*', 'FILTER_MEDIA_TYPE_ALL'], 24 ['image/*', 'FILTER_MEDIA_TYPE_IMAGE'], 25 ['video/*', 'FILTER_MEDIA_TYPE_VIDEO'], 26 ['image/movingPhoto', 'FILTER_MEDIA_TYPE_IMAGE_MOVING_PHOTO'] 27]) 28 29interface MimeTypeFilter { 30 mimeTypeArray: string[], 31 filterType: number 32} 33 34@Component 35export struct PhotoPickerComponent { 36 pickerOptions?: PickerOptions | undefined; 37 onSelect?: (uri: string) => void; 38 onDeselect?: (uri: string) => void; 39 onItemClicked?: (itemInfo: ItemInfo, clickType: ClickType) => boolean; 40 onEnterPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean; 41 onExitPhotoBrowser?: (photoBrowserInfo: PhotoBrowserInfo) => boolean; 42 onPickerControllerReady?: () => void; 43 onPhotoBrowserChanged?: (browserItemInfo: BaseItemInfo) => boolean; 44 onSelectedItemsDeleted?: ItemsDeletedCallback; 45 onExceedMaxSelected?: ExceedMaxSelectedCallback; 46 onCurrentAlbumDeleted?: CurrentAlbumDeletedCallback; 47 onVideoPlayStateChanged?: VideoPlayStateChangedCallback; 48 @ObjectLink @Watch('onChanged') pickerController: PickerController; 49 private proxy: UIExtensionProxy | undefined; 50 @State revokeIndex = 0; 51 52 private onChanged(): void { 53 if (!this.proxy) { 54 return; 55 } 56 let data = this.pickerController?.data; 57 if (data?.has('SET_SELECTED_URIS')) { 58 this.proxy.send({ 'selectUris': data?.get('SET_SELECTED_URIS') as Array<string> }); 59 console.info('PhotoPickerComponent onChanged: SET_SELECTED_URIS'); 60 } else if (data?.has('SET_ALBUM_URI')) { 61 this.proxy.send({ 'albumUri': data?.get('SET_ALBUM_URI') as string }); 62 console.info('PhotoPickerComponent onChanged: SET_ALBUM_URI'); 63 } else if (data?.has('SET_MAX_SELECT_COUNT')) { 64 this.onSetMaxSelectCount(data); 65 } else if (data?.has('SET_PHOTO_BROWSER_ITEM')) { 66 this.onSetPhotoBrowserItem(data); 67 } else { 68 this.otherOnChange(data); 69 } 70 } 71 72 private otherOnChange(data?: Map<string, Object>) { 73 if (data?.has('EXIT_PHOTO_BROWSER')) { 74 this.handleExitPhotoBrowser(); 75 } else if (data?.has('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY')) { 76 this.onSetPhotoBrowserUIElementVisibility(data); 77 } else if (data?.has('CREATE_URI')) { 78 this.onCreateUri(data); 79 console.info('PhotoPickerComponent onChanged: CREATE_URI'); 80 } else if (data?.has('REPLACE_URI')) { 81 this.onReplaceUri(data); 82 console.info('PhotoPickerComponent onChanged: REPLACE_URI'); 83 } else if (data?.has('SAVE_TRUSTED_PHOTO_ASSETS')) { 84 this.onSaveTrustedPhotoAssets(data); 85 console.info('PhotoPickerComponent onChanged: SAVE_REPLACE_PHOTO_ASSETS'); 86 } else { 87 console.info('PhotoPickerComponent onChanged: other case'); 88 } 89 } 90 91 private onSetMaxSelectCount(data?: Map<string, Object>): void { 92 let maxSelected: MaxSelected = data?.get('SET_MAX_SELECT_COUNT') as MaxSelected; 93 let map: Map<MaxCountType, number> | undefined = maxSelected?.data; 94 this.proxy.send({ 95 'totalCount': map?.get(MaxCountType.TOTAL_MAX_COUNT), 96 'photoCount': map?.get(MaxCountType.PHOTO_MAX_COUNT), 97 'videoCount': map?.get(MaxCountType.VIDEO_MAX_COUNT) 98 }); 99 console.info('PhotoPickerComponent onChanged: SET_MAX_SELECT_COUNT'); 100 } 101 102 private onSetPhotoBrowserItem(data?: Map<string, Object>): void { 103 let photoBrowserRangeInfo: PhotoBrowserRangeInfo = data?.get('SET_PHOTO_BROWSER_ITEM') as PhotoBrowserRangeInfo; 104 this.proxy?.send({ 105 'itemUri': photoBrowserRangeInfo?.uri, 106 'photoBrowserRange': photoBrowserRangeInfo?.photoBrowserRange 107 }); 108 console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_ITEM'); 109 } 110 111 private handleExitPhotoBrowser(): void { 112 this.proxy.send({ 'exitPhotoBrowser': true }); 113 console.info('PhotoPickerComponent onChanged: EXIT_PHOTO_BROWSER'); 114 } 115 116 private onSetPhotoBrowserUIElementVisibility(data?: Map<string, Object>): void { 117 let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility = 118 data?.get('SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY') as PhotoBrowserUIElementVisibility; 119 this.proxy?.send({ 120 'elements': photoBrowserUIElementVisibility?.elements, 121 'isVisible': photoBrowserUIElementVisibility?.isVisible 122 }); 123 console.info('PhotoPickerComponent onChanged: SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY'); 124 } 125 126 private onCreateUri(data?: Map<string, Object>): void { 127 let array = data?.get('CREATE_URI') as Array<Object>; 128 this.proxy?.send({ 129 selectedMediaUri: array[0], 130 createUri: array[1], 131 date: array[2] 132 }); 133 console.info('PhotoPickerComponent onChanged CREATE_URI'); 134 } 135 136 private onReplaceUri(data?: Map<string, Object>): void { 137 let array = data?.get('REPLACE_URI') as Array<Object>; 138 this.proxy?.send({ 139 oriUri: array[0], 140 replaceUri: array[1], 141 date: array[2] 142 }); 143 console.info('PhotoPickerComponent onChanged REPLACE_URI'); 144 } 145 146 private onSaveTrustedPhotoAssets(data?: Map<string, Object>): void { 147 let array: Array<object> = data?.get('SAVE_TRUSTED_PHOTO_ASSETS') as Array<object>; 148 this.proxy?.send({ 149 replaceUris: array[0], 150 config: array[1], 151 saveMode: array[2], 152 appName: array[3], 153 date: array[4] 154 }); 155 console.info('PhotoPickerComponent onChanged SAVE_REPLACE_PHOTO_ASSETS'); 156 } 157 158 build() { 159 Row() { 160 Column() { 161 SecurityUIExtensionComponent({ 162 parameters: { 163 errorRevokeIndex: this.revokeIndex, 164 "ability.want.params.uiExtensionTargetType": "photoPicker", 165 uri: "multipleselect", 166 targetPage: "photoPage", 167 filterMediaType: this.convertMIMETypeToFilterType(this.pickerOptions?.MIMEType), 168 mimeTypeFilter: this.parseMimeTypeFilter(this.pickerOptions?.mimeTypeFilter), 169 fileSizeFilter: this.pickerOptions?.fileSizeFilter, 170 videoDurationFilter: this.pickerOptions?.videoDurationFilter, 171 photoViewMimeTypeFileSizeFilters: this.pickerOptions?.photoViewMimeTypeFileSizeFilters, 172 maxSelectNumber: this.pickerOptions?.maxSelectNumber as number, 173 isPhotoTakingSupported: this.pickerOptions?.isPhotoTakingSupported as boolean, 174 isEditSupported: false, 175 recommendationOptions: this.pickerOptions?.recommendationOptions as photoAccessHelper.RecommendationOptions, 176 preselectedUri: this.pickerOptions?.preselectedUris as Array<string>, 177 isFromPickerView: true, 178 isNeedActionBar: false, 179 isNeedSelectBar: false, 180 isSearchSupported: this.pickerOptions?.isSearchSupported as boolean, 181 checkBoxColor: this.pickerOptions?.checkBoxColor as string, 182 backgroundColor: this.pickerOptions?.backgroundColor as string, 183 checkboxTextColor: this.pickerOptions?.checkboxTextColor as string, 184 photoBrowserBackgroundColorMode: this.pickerOptions?.photoBrowserBackgroundColorMode as PickerColorMode, 185 isRepeatSelectSupported: this.pickerOptions?.isRepeatSelectSupported as boolean, 186 maxSelectedReminderMode: this.pickerOptions?.maxSelectedReminderMode as ReminderMode, 187 orientation: this.pickerOptions?.orientation as PickerOrientation, 188 selectMode: this.pickerOptions?.selectMode as SelectMode, 189 maxPhotoSelectNumber: this.pickerOptions?.maxPhotoSelectNumber as number, 190 maxVideoSelectNumber: this.pickerOptions?.maxVideoSelectNumber as number, 191 isOnItemClickedSet: this.onItemClicked ? true : false, 192 isPreviewForSingleSelectionSupported: this.pickerOptions?.isPreviewForSingleSelectionSupported as boolean, 193 singleSelectionMode: this.pickerOptions?.singleSelectionMode as number, 194 isSlidingSelectionSupported: this.pickerOptions?.isSlidingSelectionSupported as boolean, 195 photoBrowserCheckboxPosition: this.pickerOptions?.photoBrowserCheckboxPosition as [number, number], 196 gridMargin: this.pickerOptions?.gridMargin as Margin, 197 photoBrowserMargin: this.pickerOptions?.photoBrowserMargin as Margin, 198 gridStartOffset: this.pickerOptions?.gridStartOffset as number, 199 gridEndOffset: this.pickerOptions?.gridEndOffset as number, 200 singleLineConfig: this.getSingleLineConfig(this.pickerOptions?.singleLineConfig as SingleLineConfig), 201 uiComponentColorMode: this.pickerOptions?.uiComponentColorMode as PickerColorMode, 202 combinedMediaTypeFilter: this.pickerOptions?.combinedMediaTypeFilter as Array<string>, 203 } 204 }) 205 .height('100%') 206 .width('100%') 207 .onRemoteReady((proxy) => { 208 this.proxy = proxy; 209 console.info('PhotoPickerComponent onRemoteReady'); 210 }) 211 .onReceive((data) => { 212 let wantParam: Record<string, Object> = data as Record<string, Object>; 213 this.handleOnReceive(wantParam); 214 }) 215 .onError((error) => { 216 console.info('PhotoPickerComponent onError: ' + JSON.stringify(error)); 217 console.info('PhotoPickerComponent revokeIndex: ' + this.revokeIndex); 218 if (error.code === 100014 && this.revokeIndex < 3) { 219 this.revokeIndex++; 220 } 221 }); 222 } 223 .width('100%') 224 } 225 .height('100%') 226 } 227 228 private handleOnReceive(wantParam: Record<string, Object>): void { 229 let dataType = wantParam['dataType'] as string; 230 console.info('PhotoPickerComponent onReceive: dataType = ' + dataType); 231 if (dataType === 'selectOrDeselect') { 232 this.handleSelectOrDeselect(wantParam); 233 } else if (dataType === 'itemClick') { 234 this.handleItemClick(wantParam); 235 } else if (dataType === 'onPhotoBrowserStateChanged') { 236 this.handleEnterOrExitPhotoBrowser(wantParam); 237 } else if (dataType === 'remoteReady') { 238 if (this.onPickerControllerReady) { 239 this.onPickerControllerReady(); 240 console.info('PhotoPickerComponent onReceive: onPickerControllerReady'); 241 } 242 } else if (dataType === 'onPhotoBrowserChanged') { 243 this.handlePhotoBrowserChange(wantParam); 244 } else if (dataType === 'onVideoPlayStateChanged') { 245 this.handleVideoPlayStateChange(wantParam) 246 } else if (dataType === 'replaceCallback') { 247 this.handleReplaceCallback(wantParam); 248 } else if (dataType === 'createCallback') { 249 this.handleCreateCallback(wantParam); 250 } else if (dataType === 'saveCallback') { 251 this.handleSaveCallback(wantParam); 252 } else if (dataType === 'onBackground') { 253 console.info('PhotoPickerComponent onReceive: onBackground'); 254 this.revokeIndex = 0; 255 } else { 256 this.handleOtherOnReceive(wantParam); 257 console.info('PhotoPickerComponent onReceive: other case'); 258 } 259 console.info('PhotoPickerComponent onReceive' + this.pickerController.encrypt(JSON.stringify(wantParam))); 260 } 261 262 private handleOtherOnReceive(wantParam: Record<string, Object>): void { 263 let dataType = wantParam.dataType as string; 264 if (dataType === 'exceedMaxSelected') { 265 if (this.onExceedMaxSelected) { 266 this.onExceedMaxSelected(wantParam.maxCountType as MaxCountType); 267 } 268 } else if (dataType === 'selectedItemsDeleted') { 269 if (this.onSelectedItemsDeleted) { 270 this.onSelectedItemsDeleted(wantParam.selectedItemInfos as Array<BaseItemInfo>); 271 } 272 } else if (dataType === 'currentAlbumDeleted') { 273 if (this.onCurrentAlbumDeleted) { 274 this.onCurrentAlbumDeleted(); 275 } 276 } else { 277 console.info('PhotoPickerComponent onReceive: other case'); 278 } 279 } 280 281 private handleSelectOrDeselect(wantParam: Record<string, Object>): void { 282 let isSelect: boolean = wantParam['isSelect'] as boolean; 283 if (isSelect) { 284 if (this.onSelect) { 285 this.onSelect(wantParam['select-item-list'] as string); 286 console.info('PhotoPickerComponent onReceive: onSelect'); 287 } 288 } else { 289 if (this.onDeselect) { 290 this.onDeselect(wantParam['select-item-list'] as string); 291 console.info('PhotoPickerComponent onReceive: onDeselect'); 292 } 293 } 294 } 295 296 private handleItemClick(wantParam: Record<string, Object>): void { 297 if (this.onItemClicked) { 298 let clickType: ClickType = ClickType.SELECTED; 299 let type = wantParam['clickType'] as string; 300 if (type === 'select') { 301 clickType = ClickType.SELECTED; 302 } else if (type === 'deselect') { 303 clickType = ClickType.DESELECTED; 304 } else { 305 console.info('PhotoPickerComponent onReceive: other clickType'); 306 } 307 let itemInfo: ItemInfo = new ItemInfo(); 308 let itemType: string = wantParam['itemType'] as string; 309 if (itemType === 'thumbnail') { 310 itemInfo.itemType = ItemType.THUMBNAIL; 311 } else if (itemType === 'camera') { 312 itemInfo.itemType = ItemType.CAMERA; 313 } else { 314 console.info('PhotoPickerComponent onReceive: other itemType'); 315 } 316 itemInfo.uri = wantParam['uri'] as string; 317 itemInfo.mimeType = wantParam['mimeType'] as string; 318 itemInfo.width = wantParam['width'] as number; 319 itemInfo.height = wantParam['height'] as number; 320 itemInfo.size = wantParam['size'] as number; 321 itemInfo.duration = wantParam['duration'] as number; 322 let result: boolean = this.onItemClicked(itemInfo, clickType); 323 console.info('PhotoPickerComponent onReceive: onItemClicked = ' + clickType); 324 if (this.proxy) { 325 if (itemType === 'thumbnail' && clickType === ClickType.SELECTED) { 326 this.proxy.send({ 'clickConfirm': itemInfo.uri, 'isConfirm': result }); 327 console.info('PhotoPickerComponent onReceive: click confirm: uri = ' + 328 this.pickerController.encrypt(itemInfo.uri) + 'isConfirm = ' + result); 329 } 330 if (itemType === 'camera') { 331 this.proxy.send({ 'enterCamera': result }); 332 console.info('PhotoPickerComponent onReceive: enter camera ' + result); 333 } 334 } 335 } 336 } 337 338 private handleEnterOrExitPhotoBrowser(wantParam: Record<string, Object>): void { 339 let isEnter: boolean = wantParam['isEnter'] as boolean; 340 let photoBrowserInfo: PhotoBrowserInfo = new PhotoBrowserInfo(); 341 photoBrowserInfo.animatorParams = new AnimatorParams(); 342 photoBrowserInfo.animatorParams.duration = wantParam['duration'] as number; 343 photoBrowserInfo.animatorParams.curve = wantParam['curve'] as Curve | ICurve | string; 344 if (isEnter) { 345 if (this.onEnterPhotoBrowser) { 346 this.onEnterPhotoBrowser(photoBrowserInfo); 347 } 348 } else { 349 if (this.onExitPhotoBrowser) { 350 this.onExitPhotoBrowser(photoBrowserInfo); 351 } 352 } 353 console.info('PhotoPickerComponent onReceive: onPhotoBrowserStateChanged = ' + isEnter); 354 } 355 356 private handlePhotoBrowserChange(wantParam: Record<string, Object>): void { 357 let browserItemInfo: BaseItemInfo = new BaseItemInfo(); 358 browserItemInfo.uri = wantParam['uri'] as string; 359 if (this.onPhotoBrowserChanged) { 360 this.onPhotoBrowserChanged(browserItemInfo); 361 } 362 console.info('PhotoPickerComponent onReceive: onPhotoBrowserChanged = ' + 363 this.pickerController.encrypt(browserItemInfo.uri)); 364 } 365 366 private handleVideoPlayStateChange(wantParam: Record<string, Object>): void { 367 if (this.onVideoPlayStateChanged) { 368 this.onVideoPlayStateChanged(wantParam.state as VideoPlayerState) 369 } 370 console.info('PhotoPickerComponent onReceive: onVideoPlayStateChanged = ' + JSON.stringify(wantParam)); 371 } 372 373 private handleCreateCallback(wantParam: Record<string, Object>): void { 374 this.pickerController.actionCreateCallback(wantParam['grantUri'] as string, wantParam['date'] as number, 375 wantParam['code'] as number, wantParam['message'] as string); 376 console.info('PhotoPickerComponent onReceive: handleCreateCallback'); 377 } 378 379 private handleReplaceCallback(wantParam: Record<string, Object>): void { 380 this.pickerController.actionReplaceCallback(wantParam['date'] as number, 381 { 'name': '', 'code': wantParam['code'] as number, 'message': wantParam['message'] as string }); 382 console.info('PhotoPickerComponent onReceive: handleReplaceCallback'); 383 } 384 385 private handleSaveCallback(wantParam: Record<string, Object>): void { 386 this.pickerController.actionSaveCallback(wantParam['date'] as number, 387 { 'name': '', 'code': wantParam['code'] as number, 'message': wantParam['error'] as string }, 388 wantParam['data'] as Array<string>); 389 console.info('PhotoPickerComponent onReceive: handleSaveCallback'); 390 } 391 392 parseMimeTypeFilter(filter?: photoAccessHelper.MimeTypeFilter): object | undefined { 393 if (!filter) { 394 return undefined; 395 } 396 let MimeTypeFilterObj: photoAccessHelper.MimeTypeFilter = { 397 mimeTypeArray: [], 398 }; 399 if (filter.mimeTypeArray) { 400 for (let mimeType of filter.mimeTypeArray) { 401 if (PHOTO_VIEW_MIME_TYPE_MAP.has(mimeType)) { 402 o.mimeTypeArray.push(PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType)); 403 } else { 404 o.mimeTypeArray.push(mimeType); 405 } 406 } 407 } 408 return MimeTypeFilterObj; 409 } 410 411 private convertMIMETypeToFilterType(mimeType: photoAccessHelper.PhotoViewMIMETypes): string { 412 let filterType: string; 413 if (PHOTO_VIEW_MIME_TYPE_MAP.has(filterType)) { 414 filterType = PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType); 415 if (PHOTO_VIEW_MIME_TYPE_MAP.has(filterType)) { 416 filterType = PHOTO_VIEW_MIME_TYPE_MAP.get(mimeType); 417 } else { 418 filterType = PHOTO_VIEW_MIME_TYPE_MAP.get('*/*); 419 filterType = PHOTO_VIEW_MIME_TYPE_MAP.get('*/*); 420 } 421 console.info('PhotoPickerComponent convertMIMETypeToFilterType: ' + JSON.stringify(filterType)); 422 return filterType; 423 } 424 425 private getSingleLineConfig(singleLineConfig: SingleLineConfig): SingleLineConfig | undefined { 426 if (singleLineConfig === null || singleLineConfig === undefined) { 427 return undefined; 428 } 429 singleLineConfig.itemDisplayRatio = (singleLineConfig.itemDisplayRatio === null || 430 singleLineConfig.itemDisplayRatio === undefined) ? ItemDisplayRatio.SQUARE_RATIO : 431 singleLineConfig.itemDisplayRatio; 432 singleLineConfig.itemBorderRadius = this.getSingleLineConfigItemBorderRadius(singleLineConfig.itemBorderRadius); 433 singleLineConfig.itemGap = this.getLength(singleLineConfig.itemGap); 434 return singleLineConfig 435 } 436 437 private getSingleLineConfigItemBorderRadius(itemBorderRadius?: Length | BorderRadiuses | 438 LocalizedBorderRadiuses): Length | BorderRadiuses | LocalizedBorderRadiuses { 439 if (itemBorderRadius === undefined || itemBorderRadius === null) { 440 return 0; 441 } 442 if (typeof itemBorderRadius === 'number' || typeof itemBorderRadius === 'string') { 443 return itemBorderRadius; 444 } 445 if (this.hasOwnProp(itemBorderRadius, ['topStart', 'topEnd', 'bottomStart', 'bottomEnd'])) { 446 const localizedBorderRadiuses: LocalizedBorderRadiuses = { 447 topStart: LengthMetrics.vp(0), 448 topEnd: LengthMetrics.vp(0), 449 bottomStart: LengthMetrics.vp(0), 450 bottomEnd: LengthMetrics.vp(0), 451 }; 452 const itemBorderRadiusValue = itemBorderRadius as LocalizedBorderRadiuses; 453 localizedBorderRadiuses.topStart = itemBorderRadiusValue.topStart ? itemBorderRadiusValue.topStart : 454 LengthMetrics.vp(0); 455 localizedBorderRadiuses.topEnd = itemBorderRadiusValue.topEnd ? itemBorderRadiusValue.topEnd : 456 LengthMetrics.vp(0); 457 localizedBorderRadiuses.bottomStart = itemBorderRadiusValue.bottomStart ? itemBorderRadiusValue.bottomStart : 458 LengthMetrics.vp(0); 459 localizedBorderRadiuses.bottomEnd = itemBorderRadiusValue.bottomEnd ? itemBorderRadiusValue.bottomEnd : 460 LengthMetrics.vp(0); 461 return localizedBorderRadiuses; 462 } 463 if (this.hasOwnProp(itemBorderRadius, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'])) { 464 const borderRadiuses: BorderRadiuses = { 465 topLeft: 0, 466 topRight: 0, 467 bottomLeft: 0, 468 bottomRight: 0 469 }; 470 const borderRadiusesValue = itemBorderRadius as BorderRadiuses; 471 borderRadiuses.topLeft = this.getLength(borderRadiusesValue.topLeft); 472 borderRadiuses.topRight = this.getLength(borderRadiusesValue.topRight); 473 borderRadiuses.bottomLeft = this.getLength(borderRadiusesValue.bottomLeft); 474 borderRadiuses.bottomRight = this.getLength(borderRadiusesValue.bottomRight); 475 return borderRadiuses; 476 } 477 const borderRadiusesValue = itemBorderRadius as Resource; 478 const resource = LengthMetrics.resource(borderRadiusesValue); 479 if (LengthUnitUtils.getInstance().isValid(resource)) { 480 return LengthUnitUtils.getInstance().stringify(resource); 481 } 482 return 0; 483 } 484 485 getLength(prop?: Length): Length { 486 if (prop === undefined || prop === null) { 487 return 0; 488 } 489 if (typeof prop === 'number' || typeof prop === 'string') { 490 return prop; 491 } 492 const resource = LengthMetrics.resource(prop); 493 if (LengthUnitUtils.getInstance().isValid(resource)) { 494 return LengthUnitUtils.getInstance().stringify(resource); 495 } 496 return 0; 497 } 498 499 private hasOwnProp(obj: Object, props: string[]): boolean { 500 for (const key of Object.keys(obj)) { 501 if (props.includes(key)) { 502 return true; 503 } 504 } 505 return false; 506 } 507} 508 509class LengthUnitUtils { 510 private static instance: LengthUnitUtils; 511 512 private constructor() { 513 } 514 515 public static getInstance(): LengthUnitUtils { 516 if (!LengthUnitUtils.instance) { 517 LengthUnitUtils.instance = new LengthUnitUtils(); 518 } 519 return LengthUnitUtils.instance; 520 } 521 522 public stringify(metrics: LengthMetrics): string { 523 if (null === metrics || undefined === metrics || typeof metrics !== 'object' || null === metrics.unit || 524 undefined === metrics.unit || null === metrics.value || undefined === metrics.value) { 525 return '0vp'; 526 } 527 switch (metrics.unit) { 528 case LengthUnit.PX: 529 return `${metrics.value}px`; 530 case LengthUnit.VP: 531 return `${metrics.value}vp`; 532 case LengthUnit.FP: 533 return `${metrics.value}fp`; 534 case LengthUnit.PERCENT: 535 return `${metrics.value}%`; 536 case LengthUnit.LPX: 537 return `${metrics.value}lpx`; 538 default: 539 return '0vp'; 540 } 541 } 542 543 public isValid(metrics: LengthMetrics): boolean { 544 if (null === metrics || undefined === metrics || typeof metrics !== 'object' || 545 null === metrics.value || undefined === metrics.value) { 546 return false; 547 } 548 return metrics.value > 0; 549 } 550} 551 552export type ItemsDeletedCallback = (baseItemInfos: Array<BaseItemInfo>) => void; 553 554export type ExceedMaxSelectedCallback = (exceedMaxCountType: MaxCountType) => void; 555 556export type CurrentAlbumDeletedCallback = () => void; 557 558export type VideoPlayStateChanged = (state: VideoPlayerState) => {} void; 559 560@Observed 561export class PickerController { 562 data?: Map<string, Object>; 563 replaceCallbackMap: Map<number, Object> = new Map<number, Object>(); 564 saveCallbackMap: Map<number, Object> = new Map<number, Object>(); 565 createCallbackMap: Map<number, Object> = new Map<number, Object>(); 566 567 setData(type: DataType, data: Object) { 568 if (data === undefined) { 569 return; 570 } 571 if (type === DataType.SET_SELECTED_URIS) { 572 if (data instanceof Array) { 573 let uriLists: Array<string> = data as Array<string>; 574 if (uriLists) { 575 this.data = new Map([['SET_SELECTED_URIS', [...uriLists]]]); 576 console.info('PhotoPickerComponent SET_SELECTED_URIS' + this.encrypt(JSON.stringify(uriLists))); 577 } 578 } 579 } else if (type === DataType.SET_ALBUM_URI) { 580 let albumUri: string = data as string; 581 if (albumUri !== undefined) { 582 this.data = new Map([['SET_ALBUM_URI', albumUri]]); 583 console.info('PhotoPickerComponent SET_ALBUM_URI' + this.encrypt(JSON.stringify(albumUri))); 584 } 585 } else { 586 console.info('PhotoPickerComponent setData: other case'); 587 } 588 } 589 590 setMaxSelected(maxSelected: MaxSelected) { 591 if (maxSelected) { 592 this.data = new Map([['SET_MAX_SELECT_COUNT', maxSelected]]); 593 console.info('PhotoPickerComponent SET_MAX_SELECT_COUNT' + JSON.stringify(maxSelected)); 594 } 595 } 596 597 setPhotoBrowserItem(uri: string, photoBrowserRange?: PhotoBrowserRange) { 598 let photoBrowserRangeInfo: PhotoBrowserRangeInfo = new PhotoBrowserRangeInfo(); 599 photoBrowserRangeInfo.uri = uri; 600 let newPhotoBrowserRange = photoBrowserRange ? photoBrowserRange : PhotoBrowserRange.ALL; 601 photoBrowserRangeInfo.photoBrowserRange = newPhotoBrowserRange; 602 this.data = new Map([['SET_PHOTO_BROWSER_ITEM', photoBrowserRangeInfo]]); 603 console.info('PhotoPickerComponent SET_PHOTO_BROWSER_ITEM' + this.encrypt(JSON.stringify(photoBrowserRangeInfo))); 604 } 605 606 exitPhotoBrowser() { 607 this.data = new Map([['EXIT_PHOTO_BROWSER', true]]); 608 console.info('PhotoPickerComponent EXIT_PHOTO_BROWSER'); 609 } 610 611 setPhotoBrowserUIElementVisibility(elements: Array<PhotoBrowserUIElement>, isVisible?: boolean) { 612 let photoBrowserUIElementVisibility: PhotoBrowserUIElementVisibility = new PhotoBrowserUIElementVisibility(); 613 photoBrowserUIElementVisibility.elements = elements; 614 photoBrowserUIElementVisibility.isVisible = isVisible; 615 this.data = new Map([['SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY', photoBrowserUIElementVisibility]]); 616 console.info('PhotoPickerComponent SET_PHOTO_BROWSER_UI_ELEMENT_VISIBILITY ' + 617 JSON.stringify(photoBrowserUIElementVisibility)); 618 } 619 620 private async getAppName(): Promise<string> { 621 let flags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_ABILITY | // for appName 622 bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE | // for appName 623 bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO | // for appId 624 bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; // for appInfo 625 let bundleInfo = bundleManager.getBundleInfoForSelfSync(flags); 626 let labelId = bundleInfo.appInfo.labelId; 627 let appName = ''; 628 let moduleName = ''; 629 for (let hapInfo of bundleInfo.hapModulesInfo) { 630 if (labelId === hapInfo.labelId) { 631 moduleName = hapInfo.name; 632 } 633 } 634 appName = await getContext(this).createModuleContext(moduleName).resourceManager.getStringValue(labelId); 635 return appName; 636 } 637 638 replacePhotoPickerPreview(selectedMediaUri: string, replaceUri: string, callback: AsyncCallback<void>) { 639 try { 640 let fd = fs.openSync(replaceUri).fd; 641 fs.close(fd); 642 } catch (err) { 643 callback({'code': 13900002, 'message': 'No such file', name: ''}); 644 return; 645 } 646 let date = Math.random(); 647 this.data = new Map([['CREATE_URI', [selectedMediaUri, replaceUri, date]]]); 648 this.createCallbackMap.set(date, (grantUri: string, code: number, message: string) => { 649 if (code !== 0) { 650 callback({ 'name': '', 'code': code, 'message': message }); 651 return; 652 } 653 let createFd = 0; 654 let replaceFd = 0; 655 try { 656 createFd = fs.openSync(grantUri, fs.OpenMode.READ_WRITE).fd; 657 replaceFd = fs.openSync(replaceUri, fs.OpenMode.READ_ONLY).fd; 658 fs.copyFileSync(replaceFd, createFd); 659 this.data = new Map([['REPLACE_URI', [selectedMediaUri, grantUri, date]]]); 660 this.replaceCallbackMap.set(date, callback); 661 } catch (err) { 662 callback({ 'code': 14000011, 'message': 'System inner fail', name: '' }); 663 } finally { 664 fs.close(createFd); 665 fs.close(replaceFd); 666 } 667 }) 668 } 669 670 saveTrustedPhotoAssets(selectedMediaUris: Array<string>, callback: AsyncCallback<Array<string>>, 671 config?: Array<photoAccessHelper.PhotoCreationConfig>, saveMode?: SaveMode) { 672 if (!selectedMediaUris || selectedMediaUris.length === 0) { 673 callback({'code': 14000002, 'message': 'Invalid URI', name: ''}, []); 674 return; 675 } 676 this.getAppName().then((appName: string)=>{ 677 let date = Math.random(); 678 this.data = new Map([['SAVE_TRUSTED_PHOTO_ASSETS', [selectedMediaUris, config, saveMode, appName, date]]]); 679 this.saveCallbackMap.set(date, callback); 680 }) 681 console.info('PhotoPickerComponent SAVE_TRUSTED_PHOTO_ASSETS '); 682 } 683 684 actionCreateCallback(grantUri: string, date: number, code: number, message: string) { 685 if (this.createCallbackMap.has(date)) { 686 let callback = this.createCallbackMap.get(date) as Function; 687 if (callback) { 688 callback(grantUri, code, message); 689 this.createCallbackMap.delete(date); 690 } 691 } 692 } 693 694 actionReplaceCallback(date: number, err: BusinessError) { 695 if (this.replaceCallbackMap.has(date)) { 696 let callback = this.replaceCallbackMap.get(date) as Function; 697 if (callback) { 698 callback(err); 699 this.replaceCallbackMap.delete(date); 700 } 701 } 702 } 703 704 actionSaveCallback(date: number, err: BusinessError, data: Array<string>) { 705 if (this.saveCallbackMap.has(date)) { 706 let callback = this.saveCallbackMap.get(date) as Function; 707 if (callback) { 708 callback(err, data); 709 this.saveCallbackMap.delete(date); 710 } 711 } 712 } 713 714 encrypt(data) { 715 if (!data || data?.indexOf('file:///data/storage/') !== -1) { 716 return ''; 717 } 718 return data.replace(/(\/\w+)\./g, '/******.'); 719 } 720} 721 722export class PickerOptions extends photoAccessHelper.BaseSelectOptions { 723 checkBoxColor?: string; 724 backgroundColor?: string; 725 isRepeatSelectSupported?: boolean; 726 checkboxTextColor?: string; 727 photoBrowserBackgroundColorMode?: PickerColorMode; 728 maxSelectedReminderMode?: ReminderMode; 729 orientation?: PickerOrientation; 730 selectMode?: SelectMode; 731 maxPhotoSelectNumber?: number; 732 maxVideoSelectNumber?: number; 733 isSlidingSelectionSupported?: boolean; 734 photoBrowserCheckboxPosition?: [number, number]; 735 gridMargin?: Margin; 736 photoBrowserMargin?: Margin; 737 singleLineConfig?: SingleLineConfig; 738 uiComponentColorMode?: PickerColorMode; 739 combinedMediaTypeFilter?: Array<string>; 740} 741 742export class BaseItemInfo { 743 uri?: string; 744 mimeType?: string; 745 width?: number; 746 height?: number; 747 size?: number; 748 duration?: number; 749} 750 751export class ItemInfo extends BaseItemInfo { 752 itemType?: ItemType; 753} 754 755export class PhotoBrowserInfo { 756 animatorParams?: AnimatorParams; 757} 758 759export class AnimatorParams { 760 duration?: number; 761 curve?: Curve | ICurve | string; 762} 763 764export class MaxSelected { 765 data?: Map<MaxCountType, number>; 766} 767 768export class SingleLineConfig { 769 itemDisplayRatio?: ItemDisplayRatio; 770 itemBorderRadius?: Length | BorderRadiuses | LocalizedBorderRadiuses; 771 itemGap?: Length; 772 773 constructor() { 774 this.itemDisplayRatio = ItemDisplayRatio.SQUARE_RATIO; 775 this.itemBorderRadius = 0; 776 this.itemGap = 0; 777 } 778} 779 780class PhotoBrowserRangeInfo { 781 uri?: string; 782 photoBrowserRange?: PhotoBrowserRange; 783} 784 785class PhotoBrowserUIElementVisibility { 786 elements?: Array<PhotoBrowserUIElement>; 787 isVisible?: boolean; 788} 789 790export enum DataType { 791 SET_SELECTED_URIS = 1, 792 SET_ALBUM_URI = 2 793} 794 795export enum ItemType { 796 THUMBNAIL = 0, 797 CAMERA = 1 798} 799 800export enum ClickType { 801 SELECTED = 0, 802 DESELECTED = 1 803} 804 805export enum PickerOrientation { 806 VERTICAL = 0, 807 HORIZONTAL = 1 808} 809 810export enum SelectMode { 811 SINGLE_SELECT = 0, 812 MULTI_SELECT = 1 813} 814 815export enum PickerColorMode { 816 AUTO = 0, 817 LIGHT = 1, 818 DARK = 2 819} 820 821export enum ReminderMode { 822 NONE = 0, 823 TOAST = 1, 824 MASK = 2 825} 826 827export enum MaxCountType { 828 TOTAL_MAX_COUNT = 0, 829 PHOTO_MAX_COUNT = 1, 830 VIDEO_MAX_COUNT = 2 831} 832 833export enum PhotoBrowserRange { 834 ALL = 0, 835 SELECTED_ONLY = 1 836} 837 838export enum PhotoBrowserUIElement { 839 CHECKBOX = 0, 840 BACK_BUTTON = 1 841} 842 843export enum VideoPlayerState { 844 PLAYING = 0, 845 PAUSED = 1, 846 STOPPED = 3, 847 SEEK_START = 4, 848 SEEK_FINISH = 5 849} 850 851export enum SaveMode { 852 SAVE_AS = 0, 853 OVERWRITE = 1 854} 855 856export enum ItemDisplayRatio { 857 SQUARE_RATIO = 0, 858 ORIGINAL_SIZE_RATIO = 1 859}