• 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 { 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}