1/* 2 * Copyright (c) 2023-2025 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 curves from '@ohos.curves'; 16import { common, EnvironmentCallback } from '@kit.AbilityKit'; 17import { BusinessError } from '@kit.BasicServicesKit'; 18import { hilog } from '@kit.PerformanceAnalysisKit'; 19import { SymbolGlyphModifier } from '@kit.ArkUI'; 20 21const START_TIME = 250; 22const END_TIME = 200; 23const BORDER_RADIUS = 12; 24const ZINDEX_NUM = 9; 25const SYMBOL_SIZE: number = 24; 26const MAX_SYMBOL_FONT_SCALE: number = 2; 27const MIN_SYMBOL_FONT_SCALE: number = 1; 28const DEFAULT_SYMBOL_FONT_SCALE: number = 1; 29 30export enum MarginType { 31 DEFAULT_MARGIN = 0, 32 FIT_MARGIN = 1, 33} 34 35export interface PromptOptions { 36 icon?: ResourceStr, 37 symbolStyle?: SymbolGlyphModifier, 38 tip?: ResourceStr, 39 marginType: MarginType, 40 actionText?: ResourceStr, 41 marginTop: Dimension, 42 isShown?: boolean 43} 44 45@Component 46export struct ExceptionPrompt { 47 @Prop options: PromptOptions 48 @State fontSizeScale: number | undefined = undefined; 49 touchBackgroundColor: Resource = $r('sys.color.ohos_id_color_sub_background_transparent') 50 maxAppFontScale: number = 1; 51 isFollowingSystemFontScale: boolean = false; 52 onTipClick?: () => void = undefined; 53 onActionTextClick?: () => void = undefined; 54 private callbackId: number | undefined = undefined; 55 private callbacks: EnvironmentCallback = { 56 onConfigurationUpdated: (config) => { 57 this.fontSizeScale = Math.min(this.updateFontScale(), MAX_SYMBOL_FONT_SCALE); 58 this.fontSizeScale = Math.max(this.fontSizeScale, MIN_SYMBOL_FONT_SCALE); 59 }, onMemoryLevel() { 60 } 61 }; 62 63 @Builder 64 TextBuilder() { 65 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign 66 .Center }) { 67 Row() { 68 if (this.options?.symbolStyle !== undefined) { 69 SymbolGlyph() 70 .fontColor([$r('sys.color.ohos_id_color_warning')]) 71 .attributeModifier(this.options?.symbolStyle) 72 .effectStrategy(SymbolEffectStrategy.NONE) 73 .symbolEffect(new SymbolEffect(), false) 74 .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) 75 } else { 76 if (Util.isSymbolResource(this.options?.icon)) { 77 SymbolGlyph((this.options?.icon as Resource | undefined) ?? $r('sys.symbol.exclamationmark_circle')) 78 .fontColor([$r('sys.color.ohos_id_color_warning')]) 79 .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) 80 } else { 81 Image(this.options?.icon) 82 .width('24vp') 83 .height('24vp') 84 .fillColor($r('sys.color.ohos_id_color_warning')) 85 } 86 } 87 Text(this.options.tip) 88 .fontSize($r('sys.float.ohos_id_text_size_body1')) 89 .minFontScale(1) 90 .maxFontScale(Math.min(this.updateFontScale(), 2)) 91 .fontColor($r('sys.color.ohos_id_color_warning')) 92 .textOverflow({ overflow: TextOverflow.Ellipsis }) 93 .maxLines(2) 94 .margin({ 95 left: $r('sys.float.ohos_id_dialog_margin_end'), 96 }) 97 .flexShrink(1) 98 } 99 .padding({ right: $r('sys.float.ohos_id_default_padding_end') }) 100 .width('100%') 101 .accessibilityDescription(this.onTipClick ? '' : ' ') 102 .onClick(() => { 103 this.onTipClick && this.onTipClick(); 104 }) 105 106 if (this.options.actionText) { 107 Button({ stateEffect: false, type: ButtonType.Normal }) { 108 Row() { 109 Text(this.options.actionText) 110 .fontSize($r('sys.float.ohos_id_text_size_body2')) 111 .minFontScale(1) 112 .maxFontScale(Math.min(this.updateFontScale(), 2)) 113 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 114 .maxLines(2) 115 .padding(0) 116 .margin({ right: $r('sys.float.ohos_id_text_paragraph_margin_s') }) 117 .textOverflow({ overflow: TextOverflow.Ellipsis }) 118 .flexShrink(1) 119 .textAlign(TextAlign.End) 120 SymbolGlyph($r('sys.symbol.chevron_right')) 121 .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) 122 .fontColor([$r('sys.color.ohos_id_color_tertiary')]) 123 } 124 .width('100%') 125 .justifyContent(FlexAlign.End) 126 } 127 .backgroundColor(this.touchBackgroundColor) 128 .width(this.options.actionText ? 144 : 0) 129 .borderRadius($r('sys.float.ohos_id_corner_radius_subtab')) 130 .padding({ 131 right: $r('sys.float.padding_level2'), 132 }) 133 .accessibilityDescription(this.onActionTextClick ? '' : ' ') 134 .accessibilityRole(this.onActionTextClick ? AccessibilityRoleType.BUTTON : AccessibilityRoleType.ROLE_NONE) 135 .onClick(() => { 136 this.onActionTextClick && this.onActionTextClick(); 137 }) 138 } 139 } 140 .padding({ 141 left: $r('sys.float.ohos_id_notification_margin_start'), 142 right: $r('sys.float.ohos_id_text_paragraph_margin_s'), 143 top: $r('sys.float.ohos_id_default_padding_start'), 144 bottom: $r('sys.float.ohos_id_default_padding_end') 145 }) 146 } 147 148 build() { 149 Row() { 150 Column() { 151 Column() { 152 this.TextBuilder() 153 } 154 .width('100%') 155 .borderRadius(BORDER_RADIUS) 156 .backgroundColor($r('sys.color.comp_background_warning_secondary')) 157 .zIndex(ZINDEX_NUM) 158 } 159 .padding(this.options.marginType === MarginType.DEFAULT_MARGIN ? { 160 left: $r('sys.float.ohos_id_card_margin_start'), 161 right: $r('sys.float.ohos_id_card_margin_end') 162 } : { 163 left: $r('sys.float.ohos_id_max_padding_start'), 164 right: $r('sys.float.ohos_id_max_padding_end') 165 }) 166 .transition( 167 TransitionEffect.OPACITY.animation({ 168 curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), 169 duration: this.options.isShown ? START_TIME : END_TIME 170 }) 171 ) 172 .visibility(this.options.isShown ? Visibility.Visible : Visibility.None) 173 } 174 .width('100%') 175 .position({ y: this.options.marginTop }) 176 .zIndex(ZINDEX_NUM) 177 } 178 179 aboutToAppear() { 180 try { 181 let uiContent: UIContext = this.getUIContext(); 182 this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); 183 this.maxAppFontScale = uiContent.getMaxFontScale(); 184 this.fontSizeScale = Math.min(this.updateFontScale(), MAX_SYMBOL_FONT_SCALE); 185 this.fontSizeScale = Math.max(this.fontSizeScale, MIN_SYMBOL_FONT_SCALE); 186 this.callbackId = uiContent.getHostContext()?.getApplicationContext()?.on('environment', this.callbacks); 187 } catch (err) { 188 let code: number = (err as BusinessError).code; 189 let message: string = (err as BusinessError).message; 190 hilog.error(0x3900, 'Ace', `Failed to init fontsizescale info, cause, code: ${code}, message: ${message}`); 191 } 192 } 193 194 aboutToDisappear(): void { 195 if (this.callbackId) { 196 this.getUIContext().getHostContext()?.getApplicationContext()?.off('environment', this.callbackId); 197 this.callbackId = void (0); 198 } 199 } 200 201 updateFontScale(): number { 202 let uiContent: UIContext = this.getUIContext(); 203 let systemFontScale: number = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; 204 if (!this.isFollowingSystemFontScale) { 205 return 1; 206 } 207 return Math.min(systemFontScale, this.maxAppFontScale); 208 } 209} 210 211class Util { 212 private static RESOURCE_TYPE_SYMBOL = 40000; 213 214 public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean { 215 if (resourceStr === undefined) { 216 return true; 217 } 218 if (!Util.isResourceType(resourceStr)) { 219 return false; 220 } 221 let resource = resourceStr as Resource; 222 return resource.type === Util.RESOURCE_TYPE_SYMBOL; 223 } 224 225 public static isResourceType(resource: ResourceStr): boolean { 226 if (!resource) { 227 return false; 228 } 229 return typeof resource !== 'string'; 230 } 231}