/* * Copyright (c) 2023-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import curves from '@ohos.curves'; import { common, EnvironmentCallback } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { LengthMetrics, SymbolGlyphModifier } from '@kit.ArkUI'; import { i18n } from '@kit.LocalizationKit'; const START_TIME = 250; const END_TIME = 200; const BORDER_RADIUS = 12; const ZINDEX_NUM = 9; const SYMBOL_SIZE: number = 24; const MAX_SYMBOL_FONT_SCALE: number = 2; const MIN_SYMBOL_FONT_SCALE: number = 1; const DEFAULT_SYMBOL_FONT_SCALE: number = 1; export enum MarginType { DEFAULT_MARGIN = 0, FIT_MARGIN = 1, } export interface PromptOptions { icon?: ResourceStr, symbolStyle?: SymbolGlyphModifier, tip?: ResourceStr, marginType: MarginType, actionText?: ResourceStr, marginTop: Dimension, isShown?: boolean } @Component export struct ExceptionPrompt { @Prop options: PromptOptions @State fontSizeScale: number | undefined = undefined; touchBackgroundColor: Resource = $r('sys.color.ohos_id_color_sub_background_transparent') maxAppFontScale: number = 1; isFollowingSystemFontScale: boolean = false; onTipClick?: () => void = undefined; onActionTextClick?: () => void = undefined; private callbackId: number | undefined = undefined; private callbacks: EnvironmentCallback = { onConfigurationUpdated: (config) => { this.fontSizeScale = Math.min(this.updateFontScale(), MAX_SYMBOL_FONT_SCALE); this.fontSizeScale = Math.max(this.fontSizeScale, MIN_SYMBOL_FONT_SCALE); }, onMemoryLevel() { } }; @Builder TextBuilder() { Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { Row() { if (this.options?.symbolStyle !== undefined) { SymbolGlyph() .fontColor([$r('sys.color.ohos_id_color_warning')]) .attributeModifier(this.options?.symbolStyle) .effectStrategy(SymbolEffectStrategy.NONE) .symbolEffect(new SymbolEffect(), false) .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) } else { if (Util.isSymbolResource(this.options?.icon)) { SymbolGlyph((this.options?.icon as Resource | undefined) ?? $r('sys.symbol.exclamationmark_circle')) .fontColor([$r('sys.color.ohos_id_color_warning')]) .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) } else { Image(this.options?.icon) .width('24vp') .height('24vp') .fillColor($r('sys.color.ohos_id_color_warning')) } } Text(this.options.tip) .fontSize($r('sys.float.ohos_id_text_size_body1')) .minFontScale(1) .maxFontScale(Math.min(this.updateFontScale(), 2)) .fontColor($r('sys.color.ohos_id_color_warning')) .textOverflow({ overflow: TextOverflow.Ellipsis }) .maxLines(2) .margin({ start: LengthMetrics.resource($r('sys.float.ohos_id_dialog_margin_end')) }) .flexShrink(1) .direction(i18n.isRTL(i18n.System.getSystemLanguage()) ? Direction.Rtl : Direction.Ltr) } .padding({ right: $r('sys.float.ohos_id_default_padding_end') }) .width('100%') .accessibilityDescription(this.onTipClick ? '' : ' ') .onClick(() => { this.onTipClick && this.onTipClick(); }) if (this.options.actionText) { Button({ stateEffect: false, type: ButtonType.Normal }) { Row() { Text(this.options.actionText) .fontSize($r('sys.float.ohos_id_text_size_body2')) .minFontScale(1) .maxFontScale(Math.min(this.updateFontScale(), 2)) .fontColor($r('sys.color.ohos_id_color_text_secondary')) .maxLines(2) .padding(0) .margin({ end: LengthMetrics.resource($r('sys.float.ohos_id_text_paragraph_margin_s')) }) .textOverflow({ overflow: TextOverflow.Ellipsis }) .flexShrink(1) .textAlign(TextAlign.End) .direction(i18n.isRTL(i18n.System.getSystemLanguage()) ? Direction.Rtl : Direction.Ltr) SymbolGlyph($r('sys.symbol.chevron_right')) .fontSize(`${(this.fontSizeScale ?? DEFAULT_SYMBOL_FONT_SCALE) * SYMBOL_SIZE}vp`) .fontColor([$r('sys.color.ohos_id_color_tertiary')]) } .width('100%') .justifyContent(FlexAlign.End) } .backgroundColor(this.touchBackgroundColor) .width(this.options.actionText ? 144 : 0) .borderRadius($r('sys.float.ohos_id_corner_radius_subtab')) .padding({ right: $r('sys.float.padding_level2'), }) .accessibilityDescription(this.onActionTextClick ? '' : ' ') .accessibilityRole(this.onActionTextClick ? AccessibilityRoleType.BUTTON : AccessibilityRoleType.ROLE_NONE) .onClick(() => { this.onActionTextClick && this.onActionTextClick(); }) } } .padding({ left: $r('sys.float.ohos_id_notification_margin_start'), right: $r('sys.float.ohos_id_text_paragraph_margin_s'), top: $r('sys.float.ohos_id_default_padding_start'), bottom: $r('sys.float.ohos_id_default_padding_end') }) } build() { Row() { Column() { Column() { this.TextBuilder() } .width('100%') .borderRadius(BORDER_RADIUS) .backgroundColor($r('sys.color.comp_background_warning_secondary')) .zIndex(ZINDEX_NUM) } .padding(this.options.marginType === MarginType.DEFAULT_MARGIN ? { left: $r('sys.float.ohos_id_card_margin_start'), right: $r('sys.float.ohos_id_card_margin_end') } : { left: $r('sys.float.ohos_id_max_padding_start'), right: $r('sys.float.ohos_id_max_padding_end') }) .transition( TransitionEffect.OPACITY.animation({ curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), duration: this.options.isShown ? START_TIME : END_TIME }) ) .visibility(this.options.isShown ? Visibility.Visible : Visibility.None) } .width('100%') .position({ y: this.options.marginTop }) .zIndex(ZINDEX_NUM) } aboutToAppear() { try { let uiContent: UIContext = this.getUIContext(); this.isFollowingSystemFontScale = uiContent.isFollowingSystemFontScale(); this.maxAppFontScale = uiContent.getMaxFontScale(); this.fontSizeScale = Math.min(this.updateFontScale(), MAX_SYMBOL_FONT_SCALE); this.fontSizeScale = Math.max(this.fontSizeScale, MIN_SYMBOL_FONT_SCALE); this.callbackId = uiContent.getHostContext()?.getApplicationContext()?.on('environment', this.callbacks); } catch (err) { let code: number = (err as BusinessError).code; let message: string = (err as BusinessError).message; hilog.error(0x3900, 'Ace', `Failed to init fontsizescale info, cause, code: ${code}, message: ${message}`); } } aboutToDisappear(): void { if (this.callbackId) { this.getUIContext().getHostContext()?.getApplicationContext()?.off('environment', this.callbackId); this.callbackId = void (0); } } updateFontScale(): number { let uiContent: UIContext = this.getUIContext(); let systemFontScale: number = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1; if (!this.isFollowingSystemFontScale) { return 1; } return Math.min(systemFontScale, this.maxAppFontScale); } } class Util { private static RESOURCE_TYPE_SYMBOL = 40000; public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean { if (resourceStr === undefined) { return true; } if (!Util.isResourceType(resourceStr)) { return false; } let resource = resourceStr as Resource; return resource.type === Util.RESOURCE_TYPE_SYMBOL; } public static isResourceType(resource: ResourceStr): boolean { if (!resource) { return false; } return typeof resource !== 'string'; } }