• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { LengthMetrics, SymbolGlyphModifier } from '@kit.ArkUI';
20import { i18n } from '@kit.LocalizationKit';
21
22const START_TIME = 250;
23const END_TIME = 200;
24const BORDER_RADIUS = 12;
25const ZINDEX_NUM = 9;
26const SYMBOL_SIZE: number = 24;
27const MAX_SYMBOL_FONT_SCALE: number = 2;
28const MIN_SYMBOL_FONT_SCALE: number = 1;
29const DEFAULT_SYMBOL_FONT_SCALE: number = 1;
30
31export enum MarginType {
32  DEFAULT_MARGIN = 0,
33  FIT_MARGIN = 1,
34}
35
36export interface PromptOptions {
37  icon?: ResourceStr,
38  symbolStyle?: SymbolGlyphModifier,
39  tip?: ResourceStr,
40  marginType: MarginType,
41  actionText?: ResourceStr,
42  marginTop: Dimension,
43  isShown?: boolean
44}
45
46@Component
47export struct ExceptionPrompt {
48  @Prop options: PromptOptions
49  @State fontSizeScale: number | undefined = undefined;
50  touchBackgroundColor: Resource = $r('sys.color.ohos_id_color_sub_background_transparent')
51  maxAppFontScale: number = 1;
52  isFollowingSystemFontScale: boolean = false;
53  onTipClick?: () => void = undefined;
54  onActionTextClick?: () => void = undefined;
55  private callbackId: number | undefined = undefined;
56  private callbacks: EnvironmentCallback = {
57    onConfigurationUpdated: (config) => {
58      this.fontSizeScale = Math.min(this.updateFontScale(), MAX_SYMBOL_FONT_SCALE);
59      this.fontSizeScale = Math.max(this.fontSizeScale, MIN_SYMBOL_FONT_SCALE);
60    }, onMemoryLevel() {
61    }
62  };
63
64  @Builder
65  TextBuilder() {
66    Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.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({ start: LengthMetrics.resource($r('sys.float.ohos_id_dialog_margin_end')) })
95          .flexShrink(1)
96          .direction(i18n.isRTL(i18n.System.getSystemLanguage()) ? Direction.Rtl : Direction.Ltr)
97      }
98      .padding({ right: $r('sys.float.ohos_id_default_padding_end') })
99      .width('100%')
100      .accessibilityDescription(this.onTipClick ? '' : ' ')
101      .onClick(() => {
102        this.onTipClick && this.onTipClick();
103      })
104
105      if (this.options.actionText) {
106        Button({ stateEffect: false, type: ButtonType.Normal }) {
107          Row() {
108            Text(this.options.actionText)
109              .fontSize($r('sys.float.ohos_id_text_size_body2'))
110              .minFontScale(1)
111              .maxFontScale(Math.min(this.updateFontScale(), 2))
112              .fontColor($r('sys.color.ohos_id_color_text_secondary'))
113              .maxLines(2)
114              .padding(0)
115              .margin({ end: LengthMetrics.resource($r('sys.float.ohos_id_text_paragraph_margin_s')) })
116              .textOverflow({ overflow: TextOverflow.Ellipsis })
117              .flexShrink(1)
118              .textAlign(TextAlign.End)
119              .direction(i18n.isRTL(i18n.System.getSystemLanguage()) ? Direction.Rtl : Direction.Ltr)
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}