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 */ 15import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 16import fs from '@ohos.file.fs'; 17import image from '@ohos.multimedia.image'; 18import media from '@ohos.multimedia.media'; 19import ability from '@ohos.ability.ability'; 20import { Constants, Log } from '@ohos/common'; 21import common from '@ohos.app.ability.common'; 22import photoAccessHelper from '@ohos.file.photoAccessHelper'; 23import { CustomContentDialog } from '@ohos.arkui.advanced.Dialog'; 24 25const TAG: string = 'SaveUIExtension'; 26let storage = LocalStorage.getShared(); 27 28@Entry(storage) 29@Component 30export struct SaveUIExtensionPage { 31 private context = getContext(this) as common.UIExtensionContext; 32 private uris: Array<string> = []; 33 private bundleName: string = ''; 34 private appName: string = ''; 35 private appId: string = ''; 36 private photoTypeArray: photoAccessHelper.PhotoType[] = []; 37 private srcFileUris: Array<photoAccessHelper.PhotoCreationConfig> = []; 38 private title: string = 'app.string.third_save_dialog_picture_and_video'; 39 private wantParam: Record<string, Object> = storage.get('wantParam') as Record<string, Object>; 40 private session: UIExtensionContentSession = 41 storage.get<UIExtensionContentSession>('session') as UIExtensionContentSession; 42 @State private imageMaps: (string | image.PixelMap)[] = ['', '']; 43 44 dialogController: CustomDialogController | null = new CustomDialogController({ 45 builder: CustomContentDialog({ 46 contentBuilder: () => { 47 this.buildContent(); 48 }, 49 contentAreaPadding: { right: 0 }, 50 buttons: [ 51 { 52 value: $r('app.string.dialog_ban'), 53 buttonStyle: ButtonStyleMode.TEXTUAL, 54 action: () => { 55 this.setSaveResult(false); 56 } 57 }, 58 { 59 value: $r('app.string.dialog_allow'), 60 buttonStyle: ButtonStyleMode.TEXTUAL, 61 action: () => { 62 this.setSaveResult(true); 63 } 64 } 65 ], 66 }), 67 autoCancel: false, 68 cancel: () => { 69 this.context.terminateSelf(); 70 } 71 }); 72 73 @Builder 74 buildContent(): void { 75 Column() { 76 Shape() { 77 Circle({ width: Constants.NUMBER_32, height: Constants.NUMBER_32 }) 78 .fill($r('sys.color.multi_color_03')) 79 Row() { 80 SymbolGlyph($r('sys.symbol.picture_fill')) 81 .fontSize(Constants.NUMBER_20) 82 .fontColor([$r('sys.color.ohos_id_color_primary_contrary')]) 83 .draggable(false) 84 } 85 .offset({ x: $r('app.float.offset_6'), y: $r('app.float.offset_6') }) 86 } 87 88 Column() { 89 Text($r(this.title, this.appName, this.uris.length)) 90 .textAlign(TextAlign.Center) 91 .fontSize($r('sys.float.Title_S')) 92 .fontWeight(FontWeight.Bold) 93 .fontColor($r('sys.color.font_primary')) 94 .textOverflow({ overflow: TextOverflow.Ellipsis }) 95 .maxLines(Constants.NUMBER_2) 96 .minFontSize($r('sys.float.Subtitle_M')) 97 .maxFontSize($r('sys.float.Title_S')) 98 .heightAdaptivePolicy(TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST) 99 } 100 .height($r('app.float.dialog_title_height')) 101 .justifyContent(FlexAlign.Center) 102 103 Stack({ alignContent: Alignment.Bottom }) { 104 if (this.uris.length > 1) { 105 Column() { 106 Image(this.imageMaps[1]) 107 .objectFit(ImageFit.Cover) 108 .border({ 109 radius: $r('app.float.radius_24'), 110 color: $r('sys.color.ohos_id_color_click_effect'), 111 width: $r('app.float.save_image_border') 112 }) 113 .height('100%') 114 .width('100%') 115 .opacity(0.4) 116 } 117 .padding({ left: $r('app.float.padding_16'), right: $r('app.float.padding_16') }) 118 } 119 120 Image(this.imageMaps[0]) 121 .objectFit(ImageFit.Cover) 122 .border({ 123 radius: $r('app.float.radius_24'), 124 color: $r('sys.color.ohos_id_color_click_effect'), 125 width: $r('app.float.save_image_border') 126 }) 127 .height(this.uris.length > 1 ? $r('app.float.third_delete_dialog_ico_height_multi') : '100%') 128 .width('100%') 129 .margin({ top: this.uris.length > 1 ? $r('app.float.margin_8') : 0 }) 130 } 131 .width('100%') 132 .height($r('app.float.third_delete_dialog_ico_height')) 133 .alignContent(Alignment.Top) 134 } 135 .alignItems(HorizontalAlign.Center) 136 .width('100%') 137 .padding({ 138 top: $r('app.float.padding_24'), 139 bottom: $r('app.float.padding_8'), 140 left: $r('app.float.dialog_content_padding_left'), 141 right: $r('app.float.dialog_content_padding_right') 142 }) 143 } 144 145 build() {} 146 147 onPageShow() { 148 this.session.setWindowBackgroundColor('#00000000'); 149 } 150 151 aboutToAppear() { 152 this.uris = this.wantParam?.['ability.params.stream'] as string[]; 153 this.bundleName = this.wantParam?.bundleName as string; 154 this.appName = this.wantParam?.appName as string; 155 this.appId = this.wantParam?.appId as string; 156 let titleArray = this.wantParam?.titleArray as string[]; 157 let extensionArray = this.wantParam?.extensionArray as string[]; 158 let photoTypeArray = this.wantParam?.photoTypeArray as photoAccessHelper.PhotoType[]; 159 let photoSubtypeArray = this.wantParam?.photoSubTypeArray as photoAccessHelper.PhotoSubtype[]; 160 this.photoTypeArray = photoTypeArray; 161 this.getImageMaps(0); 162 this.getImageMaps(1); 163 try { 164 let imageArray = photoTypeArray.filter(type => type === photoAccessHelper.PhotoType.IMAGE); 165 if (imageArray.length === photoTypeArray.length) { 166 this.title = 'app.string.third_save_dialog_picture'; 167 } else if (imageArray.length === 0) { 168 this.title = 'app.string.third_save_dialog_video'; 169 } 170 titleArray.forEach((title, idx) => { 171 let photoCreateConfig = { 172 title, 173 fileNameExtension: extensionArray[idx], 174 photoType: photoTypeArray[idx], 175 subtype: photoSubtypeArray[idx] 176 } as photoAccessHelper.PhotoCreationConfig; 177 this.srcFileUris.push(photoCreateConfig); 178 }) 179 Log.info(TAG, `srcFileUris: ${JSON.stringify(this.srcFileUris)}.`); 180 this.dialogController?.open(); 181 } catch (err) { 182 Log.error(TAG, `aboutToAppear data collection failure. err: ${JSON.stringify(err)}`); 183 this.session.terminateSelf(); 184 } 185 } 186 187 private async setSaveResult(isSave: boolean) { 188 if (isSave) { 189 let result = await this.saveBox(); 190 let abilityResult: ability.AbilityResult = { 191 resultCode: 0, 192 want: { 193 parameters: { 194 'desFileUris': result 195 } 196 } 197 }; 198 Log.info(TAG, 'terminateSelfWithResult start, abilityResult:' + JSON.stringify(abilityResult)); 199 this.session.terminateSelfWithResult(abilityResult); 200 } else { 201 let abilityResult: ability.AbilityResult = { 202 resultCode: -1 203 }; 204 Log.info(TAG, 'terminateSelfWithResult start, isSave:' + isSave); 205 this.session.terminateSelfWithResult(abilityResult); 206 } 207 } 208 209 async saveBox(): Promise<Array<string>> { 210 try { 211 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context); 212 let result: string[] = 213 await phAccessHelper.createAssetsForApp(this.bundleName, this.appName, this.appId, this.srcFileUris); 214 Log.info(TAG, `Photo agentCreateAssets: ${JSON.stringify(result)}`); 215 return result; 216 } catch (err) { 217 Log.error(TAG, `Photo agentCreateAssets failed with error: ${err.code}, ${err.message}.`); 218 return []; 219 } 220 } 221 222 private async getImageMaps(index: number) { 223 if (!this.uris) { 224 return; 225 } 226 if (index > this.uris.length - 1) { 227 return; 228 } 229 let uri: string = this.uris[index]; 230 let type: photoAccessHelper.PhotoType = this.photoTypeArray[index]; 231 let imgFile: fs.File | undefined; 232 let imageSource: image.ImageSource | undefined; 233 let avImageGenerator: media.AVImageGenerator | undefined; 234 try { 235 if (type === photoAccessHelper.PhotoType.IMAGE) { 236 imgFile = fs.openSync(uri); 237 imageSource = image.createImageSource(imgFile.fd); 238 this.imageMaps[index] = imageSource.createPixelMapSync(); 239 } else { 240 avImageGenerator = await media.createAVImageGenerator(); 241 avImageGenerator.fdSrc = fs.openSync(uri); 242 this.imageMaps[index] = 243 await avImageGenerator.fetchFrameByTime(0, media.AVImageQueryOptions.AV_IMAGE_QUERY_CLOSEST_SYNC, {}); 244 } 245 } catch (err) { 246 Log.error(TAG, `get PixelMap failed with error: ${err.code}, ${err.message}.`); 247 } finally { 248 if (type === photoAccessHelper.PhotoType.IMAGE) { 249 imageSource?.release(); 250 fs.closeSync(imgFile?.fd); 251 } else { 252 fs.closeSync(avImageGenerator?.fdSrc?.fd); 253 avImageGenerator?.release(); 254 } 255 } 256 } 257}