• 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 */
15
16import { ButtonOptions } from '@ohos.arkui.advanced.Dialog';
17import { BusinessError, Callback } from '@ohos.base';
18import display from '@ohos.display';
19import hilog from '@ohos.hilog';
20import measure from '@ohos.measure';
21import resourceManager from '@ohos.resourceManager';
22import { CustomColors, CustomTheme, Theme } from '@ohos.arkui.theme';
23import { LengthMetrics, LengthUnit } from '@ohos.arkui.node';
24import common from '@ohos.app.ability.common';
25import { accessibility } from '@kit.AccessibilityKit';
26import { KeyCode } from '@kit.InputKit';
27import { i18n } from '@kit.LocalizationKit';
28
29class CustomThemeImpl implements CustomTheme {
30  public colors?: CustomColors;
31
32  constructor(colors: CustomColors) {
33    this.colors = colors;
34  }
35}
36
37const TITLE_MAX_LINES: number = 2;
38const HORIZON_BUTTON_MAX_COUNT: number = 2;
39const VERTICAL_BUTTON_MAX_COUNT: number = 4;
40const BUTTON_LAYOUT_WEIGHT: number = 1;
41const CONTENT_MAX_LINES: number = 2;
42const LOADING_PROGRESS_WIDTH: number = 40;
43const LOADING_PROGRESS_HEIGHT: number = 40;
44const LOADING_MAX_LINES: number = 10;
45const LOADING_MAX_LINES_BIG_FONT: number = 4;
46const LOADING_TEXT_LAYOUT_WEIGHT: number = 1;
47const LOADING_TEXT_MARGIN_LEFT: number = 12;
48const LOADING_MIN_HEIGHT: number = 48;
49const LIST_MIN_HEIGHT: number = 48;
50const CHECKBOX_CONTAINER_LENGTH: number = 20;
51const TEXT_MIN_HEIGHT: number = 48;
52const MIN_CONTENT_HEIGHT: number = 100;
53const MAX_CONTENT_HEIGHT: number = 30000;
54const KEYCODE_UP: number = 2012;
55const KEYCODE_DOWN: number = 2013;
56const IGNORE_KEY_EVENT_TYPE: number = 1;
57const FIRST_ITEM_INDEX: number = 0;
58const VERSION_TWELVE: number = 50000012;
59const MAX_FONT_SCALE: number = 2;
60const FADEOUT_GRADIENT_WIDTH: number = 32;
61const FADEOUT_ENABLE: string = 'true';
62
63// 'sys.float.alert_container_max_width'
64const MAX_DIALOG_WIDTH: number = getNumberByResourceId(125831042, 400);
65// 'sys.float.alert_right_padding_horizontal'
66const BUTTON_HORIZONTAL_MARGIN: number = getNumberByResourceId(125831054, 16);
67// 'sys.float.padding_level8'
68const BUTTON_HORIZONTAL_PADDING: number = getNumberByResourceId(125830927, 16);
69// 'sys.float.alert_button_horizontal_space'
70const BUTTON_HORIZONTAL_SPACE: number = getNumberByResourceId(125831051, 8);
71// 'sys.float.Body_L'
72const BODY_L = getNumberByResourceId(125830970, 16);
73// 'sys.float.Body_M'
74const BODY_M = getNumberByResourceId(125830971, 14);
75// 'sys.float.Body_S'
76const BODY_S = getNumberByResourceId(125830972, 12);
77// 'sys.float.Title_S'
78const TITLE_S = getNumberByResourceId(125830966, 20);
79// 'sys.float.padding_level8'
80const PADDING_LEVEL_8 = getNumberByResourceId(125830927, 16);
81
82const BUTTON_MIN_FONT_SIZE = lazyInit<number>(() => {
83  return getLengthMetricsByResource($r('sys.float.dialog_button_font_min_size'), 9);
84});
85const BUTTON_MAX_FONT_SIZE = lazyInit<number>(() => {
86  return getLengthMetricsByResource($r('sys.float.dialog_button_font_max_size'), 16);
87});
88const DEFAULT_IMAGE_SIZE = lazyInit<number>(() => {
89  return getLengthMetricsByResource($r('sys.float.dialog_tip_image_size'), 64);
90});
91const DEFAULT_IMAGE_RADIUS = lazyInit<number>(() => {
92  return getLengthMetricsByResource($r('sys.float.dialog_tip_image_radius'), 12);
93});
94const TIP_TEXT_TOP_PADDING = lazyInit<number>(() => {
95  return getLengthMetricsByResource($r('sys.float.dialog_tip_text_top_padding'), 16);
96});
97const TIP_CHECKBOX_TOP_PADDING = lazyInit<number>(() => {
98  return getLengthMetricsByResource($r('sys.float.dialog_checkbox_top_padding'), 8);
99});
100const TIP_CHECKBOX_BOTTOM_PADDING = lazyInit<number>(() => {
101  return getLengthMetricsByResource($r('sys.float.dialog_checkbox_bottom_padding'), 8, true);
102});
103const TIP_CHECKBOX_END_MARGIN = lazyInit<number>(() => {
104  return getLengthMetricsByResource($r('sys.float.dialog_checkbox_end_margin'), 8);
105});
106const SUBTITLE_SIZE = lazyInit<number>(() => {
107  return getLengthMetricsByResource($r('sys.float.dialog_subtitle_font_size'), 14);
108});
109const CHECKBOX_CONTAINER_HEIGHT = lazyInit<number>(() => {
110  return getLengthMetricsByResource($r('sys.float.dialog_checkbox_min_height'), 48, true);
111});
112const CONTENT_END_MARGIN = lazyInit<number>(() => {
113  return getLengthMetricsByResource($r('sys.float.dialog_content_right_margin'), 16);
114});
115const SCROLL_END_MARGIN = lazyInit<number>(() => {
116  return getLengthMetricsByResource($r('sys.float.dialog_scroll_right_margin'), 16);
117});
118const DIALOG_DIVIDER_SHOW = lazyInit<number>(() => {
119  return getLengthMetricsByResource($r('sys.float.dialog_divider_show'), 1, true);
120});
121const ALERT_BUTTON_STYLE = lazyInit<number>(() => {
122  return getLengthMetricsByResource($r('sys.float.alert_button_style'), 2, true);
123});
124const ALERT_TITLE_ALIGNMENT = lazyInit<number>(() => {
125  return getLengthMetricsByResource($r('sys.float.alert_title_alignment'), 1);
126});
127const ERROR_BUTTON_STYLE = lazyInit<number>(() => {
128  return getLengthMetricsByResource($r('sys.float.dialog_error_button_style'), 2, true);
129});
130// 'sys.string.ohos_id_text_fadeout_enable_default'
131const IS_FADEOUT_ENABLE = lazyInit<boolean>(() => {
132  return getString(125831120) === FADEOUT_ENABLE;
133});
134// 'sys.string.dialog_title_font_weight'
135const TITLE_FONT_WEIGHT = lazyInit<FontWeight>(() => {
136  let fontWeight: FontWeight = FontWeight[getString(125834679) || 'Bold'];
137  return fontWeight;
138});
139// 'sys.string.dialog_content_font_weight'
140const CONTENT_FONT_WEIGHT = lazyInit<FontWeight>(() => {
141  let fontWeight: FontWeight = FontWeight[getString(125834682) || 'Medium'];
142  return fontWeight;
143});
144const SCROLL_BAR_OFFSET = 20;
145
146@CustomDialog
147export struct TipsDialog {
148  controller: CustomDialogController;
149  imageRes: ResourceStr | PixelMap | null = null;
150  @State imageSize?: SizeOptions = { width: DEFAULT_IMAGE_SIZE(), height: DEFAULT_IMAGE_SIZE() };
151  title?: ResourceStr | null = null;
152  content?: ResourceStr | null = null;
153  checkAction?: (isChecked: boolean) => void;
154  onCheckedChange?: Callback<boolean>;
155  checkTips?: ResourceStr | null = null;
156  @State isChecked?: boolean = false;
157  primaryButton?: ButtonOptions | null = null;
158  secondaryButton?: ButtonOptions | null = null;
159  buttons?: ButtonOptions[] | undefined = undefined;
160  @State textAlignment: TextAlign = TextAlign.Center;
161  marginOffset: number = 0;
162  // the controller of content area scroll
163  contentScroller: Scroller = new Scroller();
164  @State fontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
165  theme?: Theme | CustomTheme = new CustomThemeImpl({});
166  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
167  @State fontSizeScale: number = 1;
168  @State minContentHeight: number = 160;
169  imageIndex: number = 0;
170  textIndex: number = 1;
171  checkBoxIndex: number = 2;
172  appMaxFontScale: number = 3.2;
173
174  build() {
175    CustomDialogContentComponent({
176      controller: this.controller,
177      contentBuilder: () => {
178        this.contentBuilder();
179      },
180      buttons: this.buttons,
181      theme: this.theme,
182      themeColorMode: this.themeColorMode,
183      fontSizeScale: this.fontSizeScale,
184      minContentHeight: this.minContentHeight,
185    }).constraintSize({ maxHeight: '100%' });
186  }
187
188  @Builder
189  contentBuilder(): void {
190    TipsDialogContentLayout({
191      title: this.title,
192      content: this.content,
193      checkTips: this.checkTips,
194      minContentHeight: this.minContentHeight
195    }) {
196      ForEach([this.imageIndex, this.textIndex, this.checkBoxIndex], (index: number) => {
197        if (index === this.imageIndex) {
198          this.imagePart();
199        } else if (index === this.textIndex) {
200          Column() {
201            WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
202              this.textPart();
203            }
204          }
205          .padding({ top: TIP_TEXT_TOP_PADDING() })
206        } else {
207          WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
208            this.checkBoxPart();
209          }
210        }
211      });
212    }
213  }
214
215  @Builder
216  checkBoxPart(): void {
217    Row() {
218      if (this.checkTips !== null) {
219        Checkbox({ name: '', group: 'checkboxGroup' }).select(this.isChecked)
220          .onChange((checked: boolean) => {
221            this.isChecked = checked;
222            if (this.checkAction) {
223              this.checkAction(checked);
224            }
225            if (this.onCheckedChange) {
226              this.onCheckedChange(checked);
227            }
228          })
229          .margin({ start: LengthMetrics.vp(0), end: LengthMetrics.vp(TIP_CHECKBOX_END_MARGIN()) })
230        Text(this.checkTips)
231          .fontSize(`${BODY_L}fp`)
232          .fontWeight(FontWeight.Regular)
233          .fontColor(this.fontColorWithTheme)
234          .maxLines(CONTENT_MAX_LINES)
235          .layoutWeight(1)
236          .focusable(false)
237          .textOverflow({ overflow: TextOverflow.Ellipsis })
238      }
239    }
240    .accessibilityGroup(true)
241    .accessibilityText(getCheckTipsAccessibilityText(this.checkTips, this.isChecked))
242    .accessibilityDescription(this.isChecked ? $r('sys.string.advanced_dialog_accessibility_cancel_checked_desc') :
243    $r('sys.string.slider_accessibility_unselectedDesc'))
244    .onClick(() => {
245      this.isChecked = !this.isChecked;
246      if (this.checkAction) {
247        this.checkAction(this.isChecked);
248      }
249      try {
250        let eventInfo: accessibility.EventInfo = ({
251          type: 'announceForAccessibility',
252          bundleName: (getContext() as common.UIAbilityContext)?.abilityInfo?.bundleName,
253          triggerAction: 'common',
254          textAnnouncedForAccessibility: this.isChecked ? getContext().resourceManager.getStringSync(125833934) :
255          getContext().resourceManager.getStringSync(125833935)
256        });
257        accessibility.sendAccessibilityEvent(eventInfo).then(() => {
258        });
259      } catch (exception) {
260        let code: number = (exception as BusinessError).code;
261        let message: string = (exception as BusinessError).message;
262        hilog.error(0x3900, 'Ace', `Faild to send event, cause, code: ${code}, message: ${message}`);
263      }
264    })
265    .padding({ top: TIP_CHECKBOX_TOP_PADDING(), bottom: TIP_CHECKBOX_BOTTOM_PADDING() })
266    .constraintSize({ minHeight: CHECKBOX_CONTAINER_HEIGHT() })
267    .width('100%')
268  }
269
270  @Builder
271  imagePart(): void {
272    Column() {
273      Image(this.imageRes)
274        .objectFit(ImageFit.Contain)
275        .borderRadius(DEFAULT_IMAGE_RADIUS())
276        .constraintSize({
277          maxWidth: this.imageSize?.width ?? DEFAULT_IMAGE_SIZE(),
278          maxHeight: this.imageSize?.height ?? DEFAULT_IMAGE_SIZE()
279        })
280    }
281    .width('100%')
282  }
283
284  @Builder
285  textPart(): void {
286    Scroll(this.contentScroller) {
287      Column() {
288        if (this.title !== null) {
289          Row() {
290            Text(this.title)
291              .fontSize(`${TITLE_S}fp`)
292              .maxFontScale(Math.min(this.appMaxFontScale, MAX_FONT_SCALE))
293              .fontWeight(TITLE_FONT_WEIGHT())
294              .fontColor(this.fontColorWithTheme)
295              .textAlign(TextAlign.Center)
296              .maxLines(CONTENT_MAX_LINES)
297              .textOverflow({ overflow: TextOverflow.Ellipsis })
298              .width('100%')
299          }
300          .padding({ bottom: $r('sys.float.padding_level8') })
301        }
302        if (this.content !== null) {
303          Row() {
304            Text(this.content)
305              .focusable(true)
306              .defaultFocus(!(this.primaryButton || this.secondaryButton))
307              .focusBox({
308                strokeWidth: LengthMetrics.px(0)
309              })
310              .fontSize(this.getContentFontSize())
311              .fontWeight(CONTENT_FONT_WEIGHT())
312              .fontColor(this.fontColorWithTheme)
313              .textAlign(this.textAlignment)
314              .width('100%')
315              .onKeyEvent((event: KeyEvent) => {
316                if (event) {
317                  resolveKeyEvent(event, this.contentScroller);
318                }
319              })
320          }
321        }
322      }
323      .margin({ end: LengthMetrics.vp(CONTENT_END_MARGIN()) })
324    }
325    .fadingEdge(IS_FADEOUT_ENABLE(), { fadingEdgeLength: LengthMetrics.vp(FADEOUT_GRADIENT_WIDTH) })
326    .nestedScroll({ scrollForward: NestedScrollMode.PARALLEL, scrollBackward: NestedScrollMode.PARALLEL })
327    .margin({ end: LengthMetrics.vp(this.marginOffset) })
328  }
329
330  aboutToAppear() {
331    this.fontColorWithTheme = this.theme?.colors?.fontPrimary ?
332    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
333    let uiContext: UIContext = this.getUIContext();
334    this.appMaxFontScale = uiContext.getMaxFontScale();
335    this.initButtons();
336    this.initMargin();
337  }
338
339  getContentFontSize(): Length {
340    return BODY_L + 'fp';
341  }
342
343  private initButtons(): void {
344    if (!this.primaryButton && !this.secondaryButton) {
345      return;
346    }
347    this.buttons = [];
348    if (this.primaryButton) {
349      this.buttons.push(this.primaryButton);
350    }
351    if (this.secondaryButton) {
352      this.buttons.push(this.secondaryButton);
353    }
354  }
355
356  private initMargin(): void {
357    this.marginOffset = 0 - SCROLL_END_MARGIN();
358  }
359}
360
361@Component
362struct TipsDialogContentLayout {
363  @Builder
364  doNothingBuilder() {
365  };
366
367  title?: ResourceStr | null = null;
368  content?: ResourceStr | null = null;
369  checkTips?: ResourceStr | null = null;
370  @Link minContentHeight: number;
371  @BuilderParam dialogBuilder: () => void = this.doNothingBuilder;
372  imageIndex: number = 0;
373  textIndex: number = 1;
374  checkBoxIndex: number = 2;
375  childrenSize: number = 3;
376
377  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>,
378    constraint: ConstraintSizeOptions) {
379    let currentX: number = 0;
380    let currentY: number = 0;
381    for (let index = 0; index < children.length; index++) {
382      let child = children[index];
383      child.layout({ x: currentX, y: currentY });
384      currentY += child.measureResult.height;
385    }
386  }
387
388  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>,
389    constraint: ConstraintSizeOptions): SizeResult {
390    let sizeResult: SizeResult = { width: Number(constraint.maxWidth), height: 0 };
391    if (children.length < this.childrenSize) {
392      return sizeResult;
393    }
394    let height: number = 0;
395    let checkBoxHeight: number = 0;
396    if (this.checkTips !== null) {
397      let checkboxChild: Measurable = children[this.checkBoxIndex];
398      let checkboxConstraint: ConstraintSizeOptions = {
399        maxWidth: constraint.maxWidth,
400        minHeight: CHECKBOX_CONTAINER_HEIGHT(),
401        maxHeight: constraint.maxHeight
402      }
403      let checkBoxMeasureResult: MeasureResult = checkboxChild.measure(checkboxConstraint);
404      checkBoxHeight = checkBoxMeasureResult.height;
405      height += checkBoxHeight;
406    }
407
408    let imageChild: Measurable = children[this.imageIndex];
409    let textMinHeight: number = 0;
410    if (this.title !== null || this.content !== null) {
411      textMinHeight = TEXT_MIN_HEIGHT + PADDING_LEVEL_8;
412    }
413    let imageMaxHeight = Number(constraint.maxHeight) - checkBoxHeight - textMinHeight;
414    let imageConstraint: ConstraintSizeOptions = {
415      maxWidth: constraint.maxWidth,
416      maxHeight: imageMaxHeight
417    }
418    let imageMeasureResult: MeasureResult = imageChild.measure(imageConstraint);
419    height += imageMeasureResult.height;
420
421    if (this.title !== null || this.content !== null) {
422      let textChild: Measurable = children[this.textIndex];
423      let contentMaxHeight: number = Number(constraint.maxHeight) - imageMeasureResult.height - checkBoxHeight;
424      let contentConstraint: ConstraintSizeOptions =
425        {
426          maxWidth: constraint.maxWidth,
427          maxHeight: Math.max(contentMaxHeight, TEXT_MIN_HEIGHT)
428        };
429      let contentMeasureResult: MeasureResult = textChild.measure(contentConstraint);
430      height += contentMeasureResult.height;
431    }
432    sizeResult.height = height;
433    this.minContentHeight = Math.max(checkBoxHeight + imageMeasureResult.height + textMinHeight, MIN_CONTENT_HEIGHT);
434    return sizeResult;
435  }
436
437  build() {
438    this.dialogBuilder();
439  }
440}
441
442@CustomDialog
443export struct SelectDialog {
444  controller: CustomDialogController;
445  title: ResourceStr = '';
446  content?: ResourceStr = '';
447  confirm?: ButtonOptions | null = null;
448  radioContent: Array<SheetInfo> = [];
449  buttons?: ButtonOptions[] = [];
450  contentPadding ?: Padding;
451  isFocus: boolean = false;
452  currentFocusIndex?: number = -1;
453  radioHeight: number = 0;
454  itemHeight: number = 0;
455  @State selectedIndex?: number = -1;
456  @BuilderParam contentBuilder: () => void = this.buildContent;
457  @State fontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
458  @State dividerColorWithTheme: ResourceColor = $r('sys.color.comp_divider');
459  theme?: Theme | CustomTheme = new CustomThemeImpl({});
460  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
461  // the controller of content list
462  contentScroller: Scroller = new Scroller();
463  @State fontSizeScale: number = 1;
464  @State minContentHeight: number = MIN_CONTENT_HEIGHT;
465
466  @Styles
467  paddingContentStyle() {
468    .padding({
469      left: $r('sys.float.padding_level12'),
470      right: $r('sys.float.padding_level12'),
471      bottom: $r('sys.float.padding_level4')
472    })
473  }
474
475  @Styles
476  paddingStyle() {
477    .padding({
478      left: $r('sys.float.padding_level6'),
479      right: $r('sys.float.padding_level6')
480    })
481  }
482
483  @Builder
484  buildContent(): void {
485    Scroll(this.contentScroller) {
486      Column() {
487        if (this.content) {
488          Row() {
489            Text(this.content)
490              .fontSize(`${BODY_M}fp`)
491              .fontWeight(FontWeight.Regular)
492              .fontColor(this.fontColorWithTheme)
493              .textOverflow({ overflow: TextOverflow.Ellipsis })
494          }.paddingContentStyle().width('100%')
495        }
496        List() {
497          ForEach(this.radioContent, (item: SheetInfo, index: number) => {
498            ListItem() {
499              Column() {
500                Button() {
501                  Row() {
502                    Text(item.title)
503                      .fontSize(`${BODY_L}fp`)
504                      .fontWeight(FontWeight.Medium)
505                      .fontColor(this.fontColorWithTheme)
506                      .layoutWeight(1)
507                      .direction(i18n.isRTL(i18n.System.getSystemLanguage()) ? Direction.Rtl : Direction.Ltr)
508                    Radio({ value: 'item.title', group: 'radioGroup' })
509                      .size({ width: CHECKBOX_CONTAINER_LENGTH, height: CHECKBOX_CONTAINER_LENGTH })
510                      .checked(this.selectedIndex === index)
511                      .hitTestBehavior(HitTestMode.None)
512                      .id(String(index))
513                      .focusable(false)
514                      .accessibilityLevel('no')
515                      .visibility(this.selectedIndex === index ? Visibility.Visible : Visibility.Hidden)
516                      .radioStyle({ uncheckedBorderColor: Color.Transparent })
517                      .onFocus(() => {
518                        this.isFocus = true;
519                        this.currentFocusIndex = index;
520                        if (index === FIRST_ITEM_INDEX) {
521                          this.contentScroller.scrollEdge(Edge.Top);
522                        } else if (index === this.radioContent.length - 1) {
523                          this.contentScroller.scrollEdge(Edge.Bottom);
524                        }
525                      })
526                      .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
527                        this.radioHeight = Number(newValue.height)
528                      })
529                  }.constraintSize({ minHeight: LIST_MIN_HEIGHT }).clip(false)
530                  .padding({ top: $r('sys.float.padding_level4'), bottom: $r('sys.float.padding_level4') })
531                }
532                .type(ButtonType.Normal)
533                .borderRadius($r('sys.float.corner_radius_level8'))
534                .buttonStyle(ButtonStyleMode.TEXTUAL)
535                .paddingStyle()
536
537                if (index < this.radioContent.length - 1) {
538                  Divider().color(this.dividerColorWithTheme).paddingStyle();
539                }
540              }
541              .borderRadius($r('sys.float.corner_radius_level8'))
542              .focusBox({
543                margin: { value: -2, unit: LengthUnit.VP }
544              })
545              .accessibilityText(getAccessibilityText(item.title, this.selectedIndex === index))
546              .onClick(() => {
547                this.selectedIndex = index;
548                item.action && item.action();
549                this.controller?.close();
550              })
551            }
552            .paddingStyle()
553            .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
554              this.itemHeight = Number(newValue.height)
555            })
556          })
557        }.width('100%').clip(false)
558        .onFocus(() => {
559          if (!this.contentScroller.isAtEnd()) {
560            this.contentScroller.scrollEdge(Edge.Top);
561            focusControl.requestFocus(String(FIRST_ITEM_INDEX));
562          }
563        })
564        .defaultFocus(this.buttons?.length === 0 ? true : false)
565      }
566    }.scrollBar(BarState.Auto)
567    .nestedScroll({ scrollForward: NestedScrollMode.PARALLEL, scrollBackward: NestedScrollMode.PARALLEL })
568    .onDidScroll((xOffset: number, yOffset: number) => {
569      let scrollHeight: number = (this.itemHeight - this.radioHeight) / 2
570      if (this.isFocus) {
571        if (this.currentFocusIndex === this.radioContent.length - 1) {
572          this.contentScroller.scrollEdge(Edge.Bottom);
573          this.currentFocusIndex = -1;
574        } else if (this.currentFocusIndex === FIRST_ITEM_INDEX) {
575          this.contentScroller.scrollEdge(Edge.Top);
576          this.currentFocusIndex = -1;
577        } else {
578          if (yOffset > 0) {
579            this.contentScroller.scrollBy(0, scrollHeight)
580          } else if (yOffset < 0) {
581            this.contentScroller.scrollBy(0, 0 - scrollHeight)
582          }
583        }
584        this.isFocus = false;
585      }
586    })
587  }
588
589  build() {
590    CustomDialogContentComponent({
591      controller: this.controller,
592      primaryTitle: this.title,
593      contentBuilder: () => {
594        this.contentBuilder();
595      },
596      buttons: this.buttons,
597      contentAreaPadding: this.contentPadding,
598      theme: this.theme,
599      themeColorMode: this.themeColorMode,
600      fontSizeScale: this.fontSizeScale,
601      minContentHeight: this.minContentHeight,
602    }).constraintSize({ maxHeight: '100%' });
603  }
604
605  aboutToAppear(): void {
606    this.fontColorWithTheme = this.theme?.colors?.fontPrimary ?
607    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
608    this.dividerColorWithTheme = this.theme?.colors?.compDivider ?
609    this.theme.colors.compDivider : $r('sys.color.comp_divider');
610    this.initContentPadding();
611    this.initButtons();
612  }
613
614  private initContentPadding(): void {
615    this.contentPadding = {
616      left: $r('sys.float.padding_level0'),
617      right: $r('sys.float.padding_level0')
618    }
619
620    if (!this.title && !this.confirm) {
621      this.contentPadding = {
622        top: $r('sys.float.padding_level12'),
623        bottom: $r('sys.float.padding_level12')
624      }
625      return;
626    }
627
628    if (!this.title) {
629      this.contentPadding = {
630        top: $r('sys.float.padding_level12')
631      }
632    } else if (!this.confirm) {
633      this.contentPadding = {
634        bottom: $r('sys.float.padding_level12')
635      }
636    }
637  }
638
639  private initButtons(): void {
640    this.buttons = [];
641    if (this.confirm) {
642      this.buttons.push(this.confirm);
643    }
644  }
645}
646
647@Component
648struct ConfirmDialogContentLayout {
649  textIndex: number = 0;
650  checkboxIndex: number = 1;
651  @Link minContentHeight: number;
652
653  @Builder
654  doNothingBuilder() {
655  };
656
657  @BuilderParam dialogBuilder: () => void = this.doNothingBuilder;
658
659  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>,
660    constraint: ConstraintSizeOptions) {
661    let currentX: number = 0;
662    let currentY: number = 0;
663    for (let index = 0; index < children.length; index++) {
664      let child = children[index];
665      child.layout({ x: currentX, y: currentY });
666      currentY += child.measureResult.height;
667    }
668  }
669
670  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>,
671    constraint: ConstraintSizeOptions): SizeResult {
672    let sizeResult: SizeResult = { width: Number(constraint.maxWidth), height: 0 };
673    let childrenSize: number = 2;
674    if (children.length < childrenSize) {
675      return sizeResult;
676    }
677    let height: number = 0;
678    let checkboxChild: Measurable = children[this.checkboxIndex];
679    let checkboxConstraint: ConstraintSizeOptions = {
680      maxWidth: constraint.maxWidth,
681      minHeight: CHECKBOX_CONTAINER_HEIGHT(),
682      maxHeight: constraint.maxHeight
683    }
684    let checkBoxMeasureResult: MeasureResult = checkboxChild.measure(checkboxConstraint);
685    height += checkBoxMeasureResult.height;
686
687    let textChild: Measurable = children[this.textIndex];
688    let textConstraint: ConstraintSizeOptions = {
689      maxWidth: constraint.maxWidth,
690      maxHeight: Number(constraint.maxHeight) - height
691    }
692    let textMeasureResult: MeasureResult = textChild.measure(textConstraint);
693    height += textMeasureResult.height;
694    sizeResult.height = height;
695    this.minContentHeight = Math.max(checkBoxMeasureResult.height + TEXT_MIN_HEIGHT, MIN_CONTENT_HEIGHT);
696    return sizeResult;
697  }
698
699  build() {
700    this.dialogBuilder();
701  }
702}
703
704@CustomDialog
705export struct ConfirmDialog {
706  controller: CustomDialogController
707  title: ResourceStr = ''
708  content?: ResourceStr = ''
709  checkTips?: ResourceStr = ''
710  @State isChecked?: boolean = false
711  primaryButton?: ButtonOptions | null = null;
712  secondaryButton?: ButtonOptions | null = null;
713  @State fontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
714  theme?: Theme | CustomTheme = new CustomThemeImpl({});
715  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
716  onCheckedChange?: Callback<boolean>;
717  contentScroller: Scroller = new Scroller();
718  buttons?: ButtonOptions[] | undefined = undefined;
719  @State textAlign: TextAlign = TextAlign.Center;
720  marginOffset: number = 0;
721  @State fontSizeScale: number = 1;
722  @State minContentHeight: number = MIN_CONTENT_HEIGHT;
723  textIndex: number = 0;
724  checkboxIndex: number = 1;
725
726  @Builder
727  textBuilder(): void {
728    Column() {
729      Scroll(this.contentScroller) {
730        Column() {
731          Text(this.content)
732            .focusable(true)
733            .defaultFocus(!(this.primaryButton?.value || this.secondaryButton?.value))
734            .focusBox({
735              strokeWidth: LengthMetrics.px(0)
736            })
737            .fontSize(`${BODY_L}fp`)
738            .fontWeight(CONTENT_FONT_WEIGHT())
739            .fontColor(this.fontColorWithTheme)
740            .textAlign(this.textAlign)
741            .onKeyEvent((event: KeyEvent) => {
742              if (event) {
743                resolveKeyEvent(event, this.contentScroller);
744              }
745            })
746            .width('100%')
747        }
748        .margin({ end: LengthMetrics.vp(CONTENT_END_MARGIN()) })
749      }
750      .fadingEdge(IS_FADEOUT_ENABLE(), { fadingEdgeLength: LengthMetrics.vp(FADEOUT_GRADIENT_WIDTH) })
751      .nestedScroll({ scrollForward: NestedScrollMode.PARALLEL, scrollBackward: NestedScrollMode.PARALLEL })
752      .margin({ end: LengthMetrics.vp(this.marginOffset) })
753    }
754  }
755
756  @Builder
757  checkBoxBuilder(): void {
758    Row() {
759      Checkbox({ name: '', group: 'checkboxGroup' })
760        .select(this.isChecked)
761        .onChange((checked: boolean) => {
762          this.isChecked = checked;
763          if (this.onCheckedChange) {
764            this.onCheckedChange(this.isChecked);
765          }
766        })
767        .hitTestBehavior(HitTestMode.Block)
768        .margin({ start: LengthMetrics.vp(0), end: LengthMetrics.vp(TIP_CHECKBOX_END_MARGIN()) })
769
770      Text(this.checkTips)
771        .fontSize(`${SUBTITLE_SIZE()}fp`)
772        .fontWeight(CONTENT_FONT_WEIGHT())
773        .fontColor(this.fontColorWithTheme)
774        .maxLines(CONTENT_MAX_LINES)
775        .focusable(false)
776        .layoutWeight(1)
777        .textOverflow({ overflow: TextOverflow.Ellipsis })
778    }
779    .accessibilityGroup(true)
780    .accessibilityText(getCheckTipsAccessibilityText(this.checkTips, this.isChecked))
781    .accessibilityDescription(this.isChecked ? $r('sys.string.advanced_dialog_accessibility_cancel_checked_desc') :
782    $r('sys.string.slider_accessibility_unselectedDesc'))
783    .onClick(() => {
784      this.isChecked = !this.isChecked;
785      try {
786        let eventInfo: accessibility.EventInfo = ({
787          type: 'announceForAccessibility',
788          bundleName: (getContext() as common.UIAbilityContext)?.abilityInfo?.bundleName,
789          triggerAction: 'common',
790          textAnnouncedForAccessibility: this.isChecked ? getContext().resourceManager.getStringSync(125833934) :
791          getContext().resourceManager.getStringSync(125833935)
792        });
793        accessibility.sendAccessibilityEvent(eventInfo).then(() => {
794        });
795      } catch (exception) {
796        let code: number = (exception as BusinessError).code;
797        let message: string = (exception as BusinessError).message;
798        hilog.error(0x3900, 'Ace', `Faild to send event, cause, code: ${code}, message: ${message}`);
799      }
800    })
801    .width('100%')
802    .padding({ top: TIP_CHECKBOX_TOP_PADDING(), bottom: TIP_CHECKBOX_BOTTOM_PADDING() })
803  }
804
805  @Builder
806  buildContent(): void {
807    ConfirmDialogContentLayout({ minContentHeight: this.minContentHeight }) {
808      ForEach([this.textIndex, this.checkboxIndex], (index: number) => {
809        if (index === this.textIndex) {
810          WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
811            this.textBuilder();
812          }
813        } else if (index === this.checkboxIndex) {
814          WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
815            this.checkBoxBuilder();
816          }
817        }
818      });
819    }
820  }
821
822  build() {
823    CustomDialogContentComponent({
824      primaryTitle: this.title,
825      controller: this.controller,
826      contentBuilder: () => {
827        this.buildContent();
828      },
829      minContentHeight: this.minContentHeight,
830      buttons: this.buttons,
831      theme: this.theme,
832      themeColorMode: this.themeColorMode,
833      fontSizeScale: this.fontSizeScale,
834    }).constraintSize({ maxHeight: '100%' });
835  }
836
837  aboutToAppear(): void {
838    this.fontColorWithTheme = this.theme?.colors?.fontPrimary ?
839    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
840    this.initButtons();
841    this.initMargin();
842  }
843
844  private initMargin(): void {
845    this.marginOffset = 0 - SCROLL_END_MARGIN();
846  }
847
848  private initButtons(): void {
849    if (!this.primaryButton && !this.secondaryButton) {
850      return;
851    }
852    this.buttons = [];
853    if (this.primaryButton) {
854      this.buttons.push(this.primaryButton);
855    }
856    if (this.secondaryButton) {
857      this.buttons.push(this.secondaryButton);
858    }
859  }
860}
861
862@CustomDialog
863export struct AlertDialog {
864  controller: CustomDialogController;
865  primaryTitle?: ResourceStr | undefined = undefined;
866  secondaryTitle?: ResourceStr | undefined = undefined;
867  content: ResourceStr = '';
868  primaryButton?: ButtonOptions | null = null;
869  secondaryButton?: ButtonOptions | null = null;
870  buttons?: ButtonOptions[] | undefined = undefined;
871  @State textAlign: TextAlign = TextAlign.Center;
872  // the controller of content area
873  contentScroller: Scroller = new Scroller();
874  @State fontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
875  theme?: Theme | CustomTheme = new CustomThemeImpl({});
876  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
877  @State fontSizeScale: number = 1;
878  @State minContentHeight: number = MIN_CONTENT_HEIGHT;
879
880  build() {
881    CustomDialogContentComponent({
882      primaryTitle: this.primaryTitle,
883      secondaryTitle: this.secondaryTitle,
884      controller: this.controller,
885      contentBuilder: () => {
886        this.AlertDialogContentBuilder();
887      },
888      buttons: this.buttons,
889      theme: this.theme,
890      themeColorMode: this.themeColorMode,
891      fontSizeScale: this.fontSizeScale,
892      minContentHeight: this.minContentHeight,
893    }).constraintSize({ maxHeight: '100%' });
894  }
895
896  @Builder
897  AlertDialogContentBuilder(): void {
898    Column() {
899      Scroll(this.contentScroller) {
900        Text(this.content)
901          .focusable(true)
902          .defaultFocus(!(this.primaryButton || this.secondaryButton))
903          .focusBox({
904            strokeWidth: LengthMetrics.px(0)
905          })
906          .fontSize(`${BODY_L}fp`)
907          .fontWeight(this.getFontWeight())
908          .fontColor(this.fontColorWithTheme)
909          .margin({ end: LengthMetrics.vp(SCROLL_BAR_OFFSET) })
910          .width(`calc(100% - ${SCROLL_BAR_OFFSET}vp)`)
911          .textAlign(this.textAlign)
912          .onKeyEvent((event: KeyEvent) => {
913            if (event) {
914              resolveKeyEvent(event, this.contentScroller);
915            }
916          })
917      }
918      .fadingEdge(IS_FADEOUT_ENABLE(), { fadingEdgeLength: LengthMetrics.vp(FADEOUT_GRADIENT_WIDTH) })
919      .nestedScroll({ scrollForward: NestedScrollMode.PARALLEL, scrollBackward: NestedScrollMode.PARALLEL })
920      .width('100%')
921    }
922    .margin({ end: LengthMetrics.vp(0 - SCROLL_BAR_OFFSET) })
923  }
924
925  aboutToAppear(): void {
926    this.fontColorWithTheme = this.theme?.colors?.fontPrimary ?
927    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
928    this.initButtons();
929  }
930
931  private initButtons(): void {
932    if (!this.primaryButton && !this.secondaryButton) {
933      return;
934    }
935    this.buttons = [];
936    if (this.primaryButton) {
937      this.buttons.push(this.primaryButton);
938    }
939    if (this.secondaryButton) {
940      this.buttons.push(this.secondaryButton);
941    }
942  }
943
944  private getFontWeight(): number {
945    if (this.primaryTitle || this.secondaryTitle) {
946      return FontWeight.Regular;
947    }
948    return CONTENT_FONT_WEIGHT();
949  }
950}
951
952@CustomDialog
953export struct CustomContentDialog {
954  controller: CustomDialogController;
955  primaryTitle?: ResourceStr;
956  secondaryTitle?: ResourceStr;
957  @BuilderParam contentBuilder: () => void;
958  contentAreaPadding?: Padding;
959  localizedContentAreaPadding?: LocalizedPadding;
960  buttons?: ButtonOptions[];
961  theme?: Theme | CustomTheme = new CustomThemeImpl({});
962  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
963  @State fontSizeScale: number = 1;
964  @State minContentHeight: number = MIN_CONTENT_HEIGHT;
965
966  build() {
967    CustomDialogContentComponent({
968      controller: this.controller,
969      primaryTitle: this.primaryTitle,
970      secondaryTitle: this.secondaryTitle,
971      contentBuilder: () => {
972        this.contentBuilder();
973      },
974      contentAreaPadding: this.contentAreaPadding,
975      localizedContentAreaPadding: this.localizedContentAreaPadding,
976      buttons: this.buttons,
977      theme: this.theme,
978      themeColorMode: this.themeColorMode,
979      fontSizeScale: this.fontSizeScale,
980      minContentHeight: this.minContentHeight,
981      customStyle: false
982    }).constraintSize({ maxHeight: '100%' });
983  }
984}
985
986class CustomDialogControllerExtend extends CustomDialogController {
987  public arg_: CustomDialogControllerOptions;
988
989  constructor(value: CustomDialogControllerOptions) {
990    super(value);
991    this.arg_ = value;
992  }
993}
994
995@Component
996struct CustomDialogLayout {
997  @Builder
998  doNothingBuilder(): void {
999  };
1000
1001  @Link titleHeight: number;
1002  @Link buttonHeight: number;
1003  @Link titleMinHeight: Length;
1004  @BuilderParam dialogBuilder: () => void = this.doNothingBuilder;
1005  titleIndex: number = 0;
1006  contentIndex: number = 1;
1007  buttonIndex: number = 2;
1008
1009  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>,
1010    constraint: ConstraintSizeOptions) {
1011    let currentX: number = 0;
1012    let currentY: number = 0;
1013    for (let index = 0; index < children.length; index++) {
1014      let child = children[index];
1015      child.layout({ x: currentX, y: currentY });
1016      currentY += child.measureResult.height;
1017    }
1018  }
1019
1020  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>,
1021    constraint: ConstraintSizeOptions): SizeResult {
1022    let sizeResult: SizeResult = { width: Number(constraint.maxWidth), height: 0 };
1023    let childrenSize: number = 3;
1024    if (children.length < childrenSize) {
1025      return sizeResult;
1026    }
1027    let height: number = 0;
1028    let titleChild: Measurable = children[this.titleIndex];
1029    let titleConstraint: ConstraintSizeOptions = {
1030      maxWidth: constraint.maxWidth,
1031      minHeight: this.titleMinHeight,
1032      maxHeight: constraint.maxHeight
1033    };
1034    let titleMeasureResult: MeasureResult = titleChild.measure(titleConstraint);
1035    this.titleHeight = titleMeasureResult.height;
1036    height += this.titleHeight;
1037
1038    let buttonChild: Measurable = children[this.buttonIndex];
1039    let buttonMeasureResult: MeasureResult = buttonChild.measure(constraint);
1040    this.buttonHeight = buttonMeasureResult.height;
1041    height += this.buttonHeight;
1042
1043    let contentChild: Measurable = children[this.contentIndex];
1044    let contentConstraint: ConstraintSizeOptions = {
1045      maxWidth: constraint.maxWidth,
1046      maxHeight: Number(constraint.maxHeight) - height
1047    };
1048
1049    let contentMeasureResult: MeasureResult = contentChild.measure(contentConstraint);
1050    height += contentMeasureResult.height;
1051    sizeResult.height = height;
1052    return sizeResult;
1053  }
1054
1055  build() {
1056    this.dialogBuilder();
1057  }
1058}
1059
1060
1061@Component
1062struct CustomDialogContentComponent {
1063  controller?: CustomDialogController;
1064  primaryTitle?: ResourceStr;
1065  secondaryTitle?: ResourceStr;
1066  localizedContentAreaPadding?: LocalizedPadding;
1067  @BuilderParam contentBuilder: () => void = this.defaultContentBuilder;
1068  buttons?: ButtonOptions[];
1069  contentAreaPadding?: Padding;
1070  keyIndex: number = 0;
1071  theme?: Theme | CustomTheme = new CustomThemeImpl({});
1072  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
1073  @Link minContentHeight: number;
1074
1075  @Builder
1076  defaultContentBuilder(): void {
1077  }
1078
1079  @State titleHeight: number = 0;
1080  @State buttonHeight: number = 0;
1081  @State contentMaxHeight: Length = '100%';
1082  @Link fontSizeScale: number;
1083  @State customStyle: boolean | undefined = undefined;
1084  @State buttonMaxFontSize: Length = `${BODY_L}fp`;
1085  @State buttonMinFontSize: Length = 9;
1086  @State primaryTitleMaxFontSize: Length = `${TITLE_S}fp`;
1087  @State primaryTitleMinFontSize: Length = `${BODY_L}fp`;
1088  @State secondaryTitleMaxFontSize: Length = `${SUBTITLE_SIZE()}fp`;
1089  @State secondaryTitleMinFontSize: Length = `${BODY_S}fp`;
1090  @State primaryTitleFontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
1091  @State secondaryTitleFontColorWithTheme: ResourceColor = $r('sys.color.font_secondary');
1092  @State titleTextAlign: TextAlign = TextAlign.Center;
1093  @State isButtonVertical: boolean = false;
1094  @State titleMinHeight: Length = 0;
1095  isFollowingSystemFontScale: boolean = false;
1096  appMaxFontScale: number = 3.2;
1097  titleIndex: number = 0;
1098  contentIndex: number = 1;
1099  buttonIndex: number = 2;
1100  isHasDefaultFocus: boolean = false;
1101  isAllFocusFalse: boolean = false;
1102
1103  build() {
1104    WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
1105      Scroll() {
1106        Column() {
1107          CustomDialogLayout({
1108            buttonHeight: this.buttonHeight,
1109            titleHeight: this.titleHeight,
1110            titleMinHeight: this.titleMinHeight
1111          }) {
1112            ForEach([this.titleIndex, this.contentIndex, this.buttonIndex], (index: number) => {
1113              if (index === this.titleIndex) {
1114                WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
1115                  this.titleBuilder();
1116                }
1117              } else if (index === this.contentIndex) {
1118                Column() {
1119                  WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
1120                    this.contentBuilder();
1121                  }
1122                }.padding(this.getContentPadding())
1123              } else {
1124                WithTheme({ theme: this.theme, colorMode: this.themeColorMode }) {
1125                  this.ButtonBuilder();
1126                }
1127              }
1128            });
1129          }
1130        }
1131        .constraintSize({ maxHeight: this.contentMaxHeight })
1132        .backgroundBlurStyle(this.customStyle ? BlurStyle.Thick : BlurStyle.NONE)
1133        .borderRadius(this.customStyle ? $r('sys.float.ohos_id_corner_radius_dialog') : 0)
1134        .margin(this.customStyle ? {
1135          start: LengthMetrics.resource($r('sys.float.ohos_id_dialog_margin_start')),
1136          end: LengthMetrics.resource($r('sys.float.ohos_id_dialog_margin_end')),
1137          bottom: LengthMetrics.resource($r('sys.float.ohos_id_dialog_margin_bottom')),
1138        } : { left: 0, right: 0, bottom: 0 })
1139        .backgroundColor(this.customStyle ? $r('sys.color.ohos_id_color_dialog_bg') : Color.Transparent)
1140      }
1141      .edgeEffect(EdgeEffect.None, { alwaysEnabled: false })
1142      .backgroundColor(this.themeColorMode === ThemeColorMode.SYSTEM || undefined ?
1143      Color.Transparent : $r('sys.color.comp_background_primary'))
1144    }
1145  }
1146
1147  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>,
1148    constraint: ConstraintSizeOptions): SizeResult {
1149    let sizeResult: SizeResult = { width: selfLayoutInfo.width, height: selfLayoutInfo.height };
1150    let maxWidth: number = Number(constraint.maxWidth);
1151    let maxHeight: number = Number(constraint.maxHeight);
1152    this.fontSizeScale = this.updateFontScale();
1153    this.updateFontSize();
1154    this.isButtonVertical = this.isVerticalAlignButton(maxWidth - BUTTON_HORIZONTAL_MARGIN * 2);
1155    this.titleMinHeight = this.getTitleAreaMinHeight();
1156    let height: number = 0;
1157    children.forEach((child) => {
1158      this.contentMaxHeight = '100%';
1159      let measureResult: MeasureResult = child.measure(constraint);
1160      if (maxHeight - this.buttonHeight - this.titleHeight < this.minContentHeight) {
1161        this.contentMaxHeight = MAX_CONTENT_HEIGHT;
1162        measureResult = child.measure(constraint);
1163      }
1164      height += measureResult.height;
1165    });
1166    sizeResult.height = height;
1167    sizeResult.width = maxWidth;
1168    return sizeResult;
1169  }
1170
1171  aboutToAppear(): void {
1172    let uiContext: UIContext = this.getUIContext();
1173    this.isFollowingSystemFontScale = uiContext.isFollowingSystemFontScale();
1174    this.appMaxFontScale = uiContext.getMaxFontScale();
1175    this.fontSizeScale = this.updateFontScale();
1176    if (this.controller && this.customStyle === undefined) {
1177      let customController: CustomDialogControllerExtend = this.controller as CustomDialogControllerExtend;
1178      if (customController.arg_ && customController.arg_.customStyle && customController.arg_.customStyle === true) {
1179        this.customStyle = true;
1180      }
1181    }
1182    if (this.customStyle === undefined) {
1183      this.customStyle = false;
1184    }
1185    this.primaryTitleFontColorWithTheme = this.theme?.colors?.fontPrimary ?
1186    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
1187    this.secondaryTitleFontColorWithTheme = this.theme?.colors?.fontSecondary ?
1188    this.theme.colors.fontSecondary : $r('sys.color.font_secondary');
1189    this.initTitleTextAlign();
1190    this.setDefaultFocusState(this.buttons);
1191  }
1192
1193  private updateFontSize(): void {
1194    if (this.fontSizeScale > MAX_FONT_SCALE) {
1195      this.buttonMaxFontSize = BUTTON_MAX_FONT_SIZE() * MAX_FONT_SCALE + 'vp';
1196      this.buttonMinFontSize = BUTTON_MIN_FONT_SIZE() * MAX_FONT_SCALE + 'vp';
1197    } else {
1198      this.buttonMaxFontSize = BUTTON_MAX_FONT_SIZE() + 'fp';
1199      this.buttonMinFontSize = BUTTON_MIN_FONT_SIZE() + 'fp';
1200    }
1201  }
1202
1203  updateFontScale(): number {
1204    try {
1205      let uiContext: UIContext = this.getUIContext();
1206      let systemFontScale = (uiContext.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
1207      if (!this.isFollowingSystemFontScale) {
1208        return 1;
1209      }
1210      return Math.min(systemFontScale, this.appMaxFontScale);
1211    } catch (exception) {
1212      let code: number = (exception as BusinessError).code;
1213      let message: string = (exception as BusinessError).message;
1214      hilog.error(0x3900, 'Ace', `Faild to init fontsizescale info,cause, code: ${code}, message: ${message}`);
1215      return 1;
1216    }
1217  }
1218
1219  /**
1220   * get dialog content padding
1221   *
1222   * @returns content padding
1223   */
1224  private getContentPadding(): Padding | LocalizedPadding {
1225    if (this.localizedContentAreaPadding) {
1226      return this.localizedContentAreaPadding;
1227    }
1228    if (this.contentAreaPadding) {
1229      return this.contentAreaPadding;
1230    }
1231
1232    if ((this.primaryTitle || this.secondaryTitle) && this.buttons && this.buttons.length > 0) {
1233      return {
1234        top: 0,
1235        right: $r('sys.float.alert_content_default_padding'),
1236        bottom: 0,
1237        left: $r('sys.float.alert_content_default_padding'),
1238      };
1239    } else if (this.primaryTitle || this.secondaryTitle) {
1240      return {
1241        top: 0,
1242        right: $r('sys.float.alert_content_default_padding'),
1243        bottom: $r('sys.float.alert_content_default_padding'),
1244        left: $r('sys.float.alert_content_default_padding'),
1245      };
1246    } else if (this.buttons && this.buttons.length > 0) {
1247      return {
1248        top: $r('sys.float.alert_content_default_padding'),
1249        right: $r('sys.float.alert_content_default_padding'),
1250        bottom: 0,
1251        left: $r('sys.float.alert_content_default_padding'),
1252      };
1253    } else {
1254      return {
1255        top: $r('sys.float.alert_content_default_padding'),
1256        right: $r('sys.float.alert_content_default_padding'),
1257        bottom: $r('sys.float.alert_content_default_padding'),
1258        left: $r('sys.float.alert_content_default_padding'),
1259      };
1260    }
1261  }
1262
1263  @Builder
1264  titleBuilder() {
1265    Column() {
1266      Row() {
1267        Text(this.primaryTitle)
1268          .fontWeight(TITLE_FONT_WEIGHT())
1269          .fontColor(this.primaryTitleFontColorWithTheme)
1270          .textAlign(this.titleTextAlign)
1271          .maxFontSize(this.primaryTitleMaxFontSize)
1272          .minFontSize(this.primaryTitleMinFontSize)
1273          .maxFontScale(Math.min(this.appMaxFontScale, MAX_FONT_SCALE))
1274          .maxLines(TITLE_MAX_LINES)
1275          .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
1276          .textOverflow({ overflow: TextOverflow.Ellipsis })
1277          .width('100%')
1278      }
1279      .width('100%')
1280
1281      if (this.primaryTitle && this.secondaryTitle) {
1282        Row() {
1283        }.height($r('sys.float.padding_level1'))
1284      }
1285
1286      Row() {
1287        Text(this.secondaryTitle)
1288          .fontWeight(FontWeight.Regular)
1289          .fontColor(this.secondaryTitleFontColorWithTheme)
1290          .textAlign(this.titleTextAlign)
1291          .maxFontSize(this.secondaryTitleMaxFontSize)
1292          .minFontSize(this.secondaryTitleMinFontSize)
1293          .maxFontScale(Math.min(this.appMaxFontScale, MAX_FONT_SCALE))
1294          .maxLines(TITLE_MAX_LINES)
1295          .heightAdaptivePolicy(TextHeightAdaptivePolicy.MAX_LINES_FIRST)
1296          .textOverflow({ overflow: TextOverflow.Ellipsis })
1297          .width('100%')
1298      }
1299      .width('100%')
1300    }
1301    .justifyContent(FlexAlign.Center)
1302    .width('100%')
1303    .padding(this.getTitleAreaPadding())
1304  }
1305
1306  /**
1307   * get title area padding
1308   *
1309   * @returns padding
1310   */
1311  private getTitleAreaPadding(): Padding {
1312    if (this.primaryTitle || this.secondaryTitle) {
1313      return {
1314        top: $r('sys.float.alert_title_padding_top'),
1315        right: $r('sys.float.alert_title_padding_right'),
1316        left: $r('sys.float.alert_title_padding_left'),
1317        bottom: $r('sys.float.alert_title_padding_bottom'),
1318      };
1319    }
1320
1321    return {
1322      top: 0,
1323      right: $r('sys.float.alert_title_padding_right'),
1324      left: $r('sys.float.alert_title_padding_left'),
1325      bottom: 0,
1326    };
1327  }
1328
1329  /**
1330   * get tile TextAlign
1331   * @returns TextAlign
1332   */
1333  private initTitleTextAlign(): void {
1334    let textAlign: number = ALERT_TITLE_ALIGNMENT();
1335    if (textAlign === TextAlign.Start) {
1336      this.titleTextAlign = TextAlign.Start;
1337    } else if (textAlign === TextAlign.Center) {
1338      this.titleTextAlign = TextAlign.Center;
1339    } else if (textAlign === TextAlign.End) {
1340      this.titleTextAlign = TextAlign.End;
1341    } else if (textAlign === TextAlign.JUSTIFY) {
1342      this.titleTextAlign = TextAlign.JUSTIFY;
1343    } else {
1344      this.titleTextAlign = TextAlign.Center;
1345    }
1346  }
1347
1348  /**
1349   * get title area min height
1350   *
1351   * @returns min height
1352   */
1353  private getTitleAreaMinHeight(): ResourceStr | number {
1354    if (this.secondaryTitle) {
1355      return $r('sys.float.alert_title_secondary_height');
1356    } else if (this.primaryTitle) {
1357      return $r('sys.float.alert_title_primary_height');
1358    } else {
1359      return 0;
1360    }
1361  }
1362
1363  /**
1364   * set state of button focus
1365   */
1366  private setDefaultFocusState(buttonList?: ButtonOptions[]): void {
1367    if (!buttonList) {
1368      return;
1369    }
1370    let falseNum: number = 0;
1371    buttonList.forEach((button: ButtonOptions) => {
1372      // 遍历查询按钮中存在是否存在默认按钮
1373      if (button.defaultFocus) {
1374        this.isHasDefaultFocus = true;
1375      }
1376      if (button.defaultFocus === false) {
1377        falseNum++;
1378      }
1379    });
1380    // 所有按钮defaultFocus都设置为false
1381    if (falseNum === buttonList.length) {
1382      this.isAllFocusFalse = true;
1383    }
1384  }
1385
1386  @Builder
1387  ButtonBuilder(): void {
1388    Column() {
1389      if (this.buttons && this.buttons.length > 0) {
1390        if (this.isButtonVertical) {
1391          this.buildVerticalAlignButtons();
1392        } else {
1393          this.buildHorizontalAlignButtons();
1394        }
1395      }
1396    }
1397    .width('100%')
1398    .padding(this.getOperationAreaPadding());
1399  }
1400
1401  /**
1402   * get operation area padding
1403   *
1404   * @returns padding
1405   */
1406  private getOperationAreaPadding(): Padding {
1407    if (this.isButtonVertical) {
1408      return {
1409        top: $r('sys.float.alert_button_top_padding'),
1410        right: $r('sys.float.alert_right_padding_vertical'),
1411        left: $r('sys.float.alert_left_padding_vertical'),
1412        bottom: $r('sys.float.alert_button_bottom_padding_vertical'),
1413      };
1414    }
1415
1416    return {
1417      top: $r('sys.float.alert_button_top_padding'),
1418      right: $r('sys.float.alert_right_padding_horizontal'),
1419      left: $r('sys.float.alert_left_padding_horizontal'),
1420      bottom: $r('sys.float.alert_button_bottom_padding_horizontal'),
1421    };
1422  }
1423
1424  @Builder
1425  buildSingleButton(buttonOptions: ButtonOptions): void {
1426    if (this.isNewPropertiesHighPriority(buttonOptions)) {
1427      Button(buttonOptions.value)
1428        .setButtonProperties(buttonOptions, this.isHasDefaultFocus, this.isAllFocusFalse, this.controller)
1429        .role(buttonOptions.role ?? ButtonRole.NORMAL)
1430        .key(`advanced_dialog_button_${this.keyIndex++}`)
1431        .labelStyle({
1432          maxLines: 1,
1433          overflow: IS_FADEOUT_ENABLE() ? TextOverflow.MARQUEE : TextOverflow.Ellipsis,
1434          maxFontSize: this.buttonMaxFontSize,
1435          minFontSize: this.buttonMinFontSize
1436        })
1437    } else if (buttonOptions.background !== undefined && buttonOptions.fontColor !== undefined) {
1438      Button(buttonOptions.value)
1439        .setButtonProperties(buttonOptions, this.isHasDefaultFocus, this.isAllFocusFalse, this.controller)
1440        .backgroundColor(buttonOptions.background)
1441        .fontColor(buttonOptions.fontColor)
1442        .key(`advanced_dialog_button_${this.keyIndex++}`)
1443        .labelStyle({
1444          maxLines: 1,
1445          overflow: IS_FADEOUT_ENABLE() ? TextOverflow.MARQUEE : TextOverflow.Ellipsis,
1446          maxFontSize: this.buttonMaxFontSize,
1447          minFontSize: this.buttonMinFontSize
1448        })
1449    } else if (buttonOptions.background !== undefined) {
1450      Button(buttonOptions.value)
1451        .setButtonProperties(buttonOptions, this.isHasDefaultFocus, this.isAllFocusFalse, this.controller)
1452        .backgroundColor(buttonOptions.background)
1453        .key(`advanced_dialog_button_${this.keyIndex++}`)
1454        .labelStyle({
1455          maxLines: 1,
1456          overflow: IS_FADEOUT_ENABLE() ? TextOverflow.MARQUEE : TextOverflow.Ellipsis,
1457          maxFontSize: this.buttonMaxFontSize,
1458          minFontSize: this.buttonMinFontSize
1459        })
1460    } else {
1461      Button(buttonOptions.value)
1462        .setButtonProperties(buttonOptions, this.isHasDefaultFocus, this.isAllFocusFalse, this.controller)
1463        .fontColor(buttonOptions.fontColor)
1464        .key(`advanced_dialog_button_${this.keyIndex++}`)
1465        .labelStyle({
1466          maxLines: 1,
1467          overflow: IS_FADEOUT_ENABLE() ? TextOverflow.MARQUEE : TextOverflow.Ellipsis,
1468          maxFontSize: this.buttonMaxFontSize,
1469          minFontSize: this.buttonMinFontSize
1470        })
1471    }
1472  }
1473
1474  @Builder
1475  buildHorizontalAlignButtons(): void {
1476    if (this.buttons && this.buttons.length > 0) {
1477      Row() {
1478        this.buildSingleButton(this.buttons[0]);
1479        if (this.buttons.length === HORIZON_BUTTON_MAX_COUNT) {
1480          Row() {
1481            Divider()
1482              .width($r('sys.float.alert_divider_width'))
1483              .height($r('sys.float.alert_divider_height'))
1484              .color(this.getDividerColor())
1485              .vertical(true)
1486          }
1487          .width(BUTTON_HORIZONTAL_SPACE * 2)
1488          .justifyContent(FlexAlign.Center)
1489
1490          this.buildSingleButton(this.buttons[HORIZON_BUTTON_MAX_COUNT - 1]);
1491        }
1492      }
1493    }
1494  }
1495
1496  @Builder
1497  buildVerticalAlignButtons(): void {
1498    if (this.buttons) {
1499      Column() {
1500        ForEach(this.buttons.slice(0, VERTICAL_BUTTON_MAX_COUNT), (item: ButtonOptions, index: number) => {
1501          this.buildButtonWithDivider(this.buttons?.length === HORIZON_BUTTON_MAX_COUNT ?
1502            HORIZON_BUTTON_MAX_COUNT - index - 1 : index);
1503        }, (item: ButtonOptions) => item.value.toString());
1504      }
1505    }
1506  }
1507
1508  /**
1509   * get divider color
1510   *
1511   * @returns divider color
1512   */
1513  private getDividerColor(): ResourceColor {
1514    if (!this.buttons || this.buttons.length === 0 || !DIALOG_DIVIDER_SHOW()) {
1515      return Color.Transparent;
1516    }
1517
1518    if (this.buttons[0].buttonStyle === ButtonStyleMode.TEXTUAL || this.buttons[0].buttonStyle === undefined) {
1519      if (this.buttons[HORIZON_BUTTON_MAX_COUNT - 1].buttonStyle === ButtonStyleMode.TEXTUAL ||
1520        this.buttons[HORIZON_BUTTON_MAX_COUNT - 1].buttonStyle === undefined) {
1521        return $r('sys.color.alert_divider_color');
1522      }
1523    }
1524    return Color.Transparent;
1525  }
1526
1527  /**
1528   * is button buttonStyle and role properties high priority
1529   *
1530   * @param buttonOptions button properties
1531   * @returns check result
1532   */
1533  private isNewPropertiesHighPriority(buttonOptions: ButtonOptions): boolean {
1534    if (buttonOptions.role === ButtonRole.ERROR) {
1535      return true;
1536    }
1537    if (buttonOptions.buttonStyle !== undefined &&
1538      buttonOptions.buttonStyle !== ALERT_BUTTON_STYLE()) {
1539      return true;
1540    }
1541    if (buttonOptions.background === undefined && buttonOptions.fontColor === undefined) {
1542      return true;
1543    }
1544    return false;
1545  }
1546
1547  @Builder
1548  buildButtonWithDivider(index: number): void {
1549    if (this.buttons && this.buttons[index]) {
1550      Row() {
1551        this.buildSingleButton(this.buttons[index]);
1552      }
1553
1554      if ((this.buttons.length === HORIZON_BUTTON_MAX_COUNT ? HORIZON_BUTTON_MAX_COUNT - index - 1 : index) <
1555        Math.min(this.buttons.length, VERTICAL_BUTTON_MAX_COUNT) - 1) {
1556        Row() {
1557        }
1558        .height($r('sys.float.alert_button_vertical_space'))
1559      }
1560    }
1561  }
1562
1563  private isVerticalAlignButton(width: number): boolean {
1564    if (this.buttons) {
1565      if (this.buttons.length === 1) {
1566        return false;
1567      }
1568      if (this.buttons.length !== HORIZON_BUTTON_MAX_COUNT) {
1569        return true;
1570      }
1571      let isVertical: boolean = false;
1572      let maxButtonTextSize = vp2px(width / HORIZON_BUTTON_MAX_COUNT - BUTTON_HORIZONTAL_MARGIN -
1573        BUTTON_HORIZONTAL_SPACE - 2 * BUTTON_HORIZONTAL_PADDING);
1574      this.buttons.forEach((button) => {
1575        try {
1576          let contentSize: SizeOptions = measure.measureTextSize({
1577            textContent: button.value,
1578            fontSize: this.buttonMaxFontSize
1579          });
1580          if (Number(contentSize?.width) > maxButtonTextSize) {
1581            isVertical = true;
1582          }
1583        } catch (err) {
1584          let code: number = (err as BusinessError).code;
1585          let message: string = (err as BusinessError).message;
1586          hilog.error(0x3900, 'Ace',
1587            `Faild to dialog isVerticalAlignButton measureTextSize,cause, code: ${code}, message: ${message}`);
1588        }
1589      });
1590      return isVertical;
1591    }
1592    return false;
1593  }
1594}
1595
1596@Extend(Button)
1597function setButtonProperties(buttonOptions: ButtonOptions, isHasDefaultFocus: boolean, isAllFocusFalse: boolean,
1598  controller?: CustomDialogController) {
1599  .onKeyEvent((event: KeyEvent) => {
1600    if (!event) {
1601      return;
1602    }
1603    if ((event.keyCode === KeyCode.KEYCODE_SPACE || event.keyCode === KeyCode.KEYCODE_ENTER) &&
1604      event.type === KeyType.Down) {
1605      if (buttonOptions.action) {
1606        buttonOptions.action();
1607      }
1608      controller?.close();
1609      event.stopPropagation();
1610    }
1611  })
1612  .onClick(() => {
1613    if (buttonOptions.action) {
1614      buttonOptions.action();
1615    }
1616    controller?.close();
1617  })
1618  .defaultFocus(isDefaultFocus(buttonOptions, isHasDefaultFocus, isAllFocusFalse))
1619  .buttonStyle(buttonOptions.buttonStyle ??
1620    (buttonOptions.role === ButtonRole.ERROR ? ERROR_BUTTON_STYLE() : ALERT_BUTTON_STYLE()))
1621  .layoutWeight(BUTTON_LAYOUT_WEIGHT)
1622  .type(ButtonType.ROUNDED_RECTANGLE)
1623}
1624
1625/**
1626 * is button set default focus
1627 *
1628 * @param singleButton button options
1629 * @param isHasDefaultFocus is button list has default focus button
1630 * @param isAllFocusFalse is all button in button list default focus false
1631 * @returns boolean
1632 */
1633function isDefaultFocus(singleButton: ButtonOptions, isHasDefaultFocus: boolean, isAllFocusFalse: boolean): boolean {
1634  try {
1635    // 当前按钮为默认按钮
1636    if (singleButton.defaultFocus) {
1637      return true;
1638    }
1639    let isDefaultFocus: boolean = false;
1640    if (isHasDefaultFocus || isAllFocusFalse) {
1641      isDefaultFocus = false; // 存在默认按钮或者所有按钮的defaultFocus都为false
1642    } else {
1643      isDefaultFocus = true; // 默认第一个按钮获焦
1644    }
1645    return isDefaultFocus;
1646  } catch (error) {
1647    let code: number = (error as BusinessError).code;
1648    let message: string = (error as BusinessError).message;
1649    hilog.error(0x3900, 'Ace', `get defaultFocus exist error, code: ${code}, message: ${message}`);
1650    return true;
1651  }
1652}
1653
1654/**
1655 * get resource size
1656 *
1657 * @param resourceId resource id
1658 * @param defaultValue default value
1659 * @returns resource size
1660 */
1661function getNumberByResourceId(resourceId: number, defaultValue: number, allowZero?: boolean): number {
1662  try {
1663    let sourceValue: number = resourceManager.getSystemResourceManager().getNumber(resourceId);
1664    if (sourceValue > 0 || allowZero) {
1665      return sourceValue;
1666    } else {
1667      return defaultValue;
1668    }
1669  } catch (error) {
1670    let code: number = (error as BusinessError).code;
1671    let message: string = (error as BusinessError).message;
1672    hilog.error(0x3900, 'Ace', `CustomContentDialog getNumberByResourceId error, code: ${code}, message: ${message}`);
1673    return defaultValue;
1674  }
1675}
1676
1677/**
1678 * lazy init
1679 *
1680 * @param initializer  lazy initializer
1681 * @returns lazy init result
1682 */
1683function lazyInit<T>(initializer: () => T): () => T {
1684  let value: T | null = null;
1685  return () => {
1686    if (value === null) {
1687      value = initializer();
1688    }
1689    return value;
1690  };
1691}
1692
1693/**
1694 * get LengthMetrics size
1695 *
1696 * @param resource resource
1697 * @param defaultValue default value
1698 * @param isAllowZero allow value zero
1699 * @returns LengthMetrics size
1700 */
1701function getLengthMetricsByResource(resource: Resource, defaultValue: number, isAllowZero?: boolean): number {
1702  if (!resource) {
1703    hilog.error(0x3900, 'Ace', 'CustomContentDialog getLengthMetricsByResource error');
1704    return defaultValue;
1705  }
1706  try {
1707    let sourceValue: number = LengthMetrics.resource(resource).value;
1708    if (sourceValue === 0) {
1709      return isAllowZero ? sourceValue : defaultValue;
1710    }
1711    return sourceValue;
1712  } catch (error) {
1713    let code: number = (error as BusinessError).code;
1714    let message: string = (error as BusinessError).message;
1715    hilog.error(0x3900, 'Ace',
1716      `CustomContentDialog getLengthMetricsByResource error, code: ${code}, message: ${message}`);
1717    return defaultValue;
1718  }
1719}
1720
1721/**
1722 * get string value
1723 *
1724 * @param resourceId Resource id
1725 * @returns resource value
1726 */
1727function getString(resourceId: number): string {
1728  let res = '';
1729  if (resourceId <= 0) {
1730    hilog.error(0x3900, 'Ace', 'CustomContentDialog getString error');
1731    return res;
1732  }
1733  try {
1734    res = getContext().resourceManager.getStringSync(resourceId);
1735  } catch (error) {
1736    let code: number = (error as BusinessError).code;
1737    let message: string = (error as BusinessError).message;
1738    hilog.error(0x3900, 'Ace',
1739      `CustomContentDialog getString error, code: ${code}, message: ${message}`);
1740  }
1741  return res;
1742}
1743
1744/**
1745 * 获取SelectDialog无障碍文本
1746 *
1747 * @param resource 资源
1748 * @param selected select state
1749 * @returns string
1750 */
1751function getAccessibilityText(resource: ResourceStr, selected: boolean): string {
1752  try {
1753    let selectText: string = getContext().resourceManager.getStringSync(125833934);
1754    let resourceString: string = '';
1755    if (typeof resource === 'string') {
1756      resourceString = resource;
1757    } else {
1758      resourceString = getContext().resourceManager.getStringSync(resource);
1759    }
1760    return selected ? `${selectText},${resourceString}` : resourceString;
1761  } catch (error) {
1762    let code: number = (error as BusinessError).code;
1763    let message: string = (error as BusinessError).message;
1764    hilog.error(0x3900, 'Ace', `getAccessibilityText error, code: ${code}, message: ${message}`);
1765    return '';
1766  }
1767}
1768
1769/**
1770 * resolve content area keyEvent
1771 *
1772 * @param event keyEvent
1773 * @param controller the controller of content area
1774 * @returns undefined
1775 */
1776function resolveKeyEvent(event: KeyEvent, controller: Scroller) {
1777  if (event.type === IGNORE_KEY_EVENT_TYPE) {
1778    return;
1779  }
1780
1781  if (event.keyCode === KEYCODE_UP) {
1782    controller.scrollPage({ next: false });
1783    event.stopPropagation();
1784  } else if (event.keyCode === KEYCODE_DOWN) {
1785    if (controller.isAtEnd()) {
1786      return;
1787    } else {
1788      controller.scrollPage({ next: true });
1789      event.stopPropagation();
1790    }
1791  }
1792}
1793
1794/**
1795 * 获取checkTips无障碍文本
1796 *
1797 * @param resource 资源
1798 * @param selected select state
1799 * @returns string
1800 */
1801function getCheckTipsAccessibilityText(resource: ResourceStr | null | undefined, selected?: boolean): string {
1802  try {
1803    // 'sys.string.slider_accessibility_selected'
1804    let selectText: string = getContext().resourceManager.getStringSync(125833934);
1805    // 'sys.string.slider_accessibility_unselected'
1806    let unselectText: string = getContext().resourceManager.getStringSync(125833935);
1807    // 'sys.string.advanced_dialog_accessibility_checkbox'
1808    let checkBoxText: string = getContext().resourceManager.getStringSync(125834354);
1809    let resourceString: string = '';
1810    if (typeof resource === 'string') {
1811      resourceString = resource
1812    } else {
1813      resourceString = getContext().resourceManager.getStringSync(resource);
1814    }
1815    return selected ? `${selectText},${resourceString},${checkBoxText}` :
1816      `${unselectText},${resourceString},${checkBoxText}`;
1817  } catch (error) {
1818    let code: number = (error as BusinessError).code;
1819    let message: string = (error as BusinessError).message;
1820    hilog.error(0x3900, 'Ace', `getCheckTipsAccessibilityText error, code: ${code}, message: ${message}`);
1821    return '';
1822  }
1823}
1824
1825@CustomDialog
1826export struct LoadingDialog {
1827  controller: CustomDialogController;
1828  content?: ResourceStr = '';
1829  @State fontColorWithTheme: ResourceColor = $r('sys.color.font_primary');
1830  @State loadingProgressIconColorWithTheme: ResourceColor = $r('sys.color.icon_secondary');
1831  theme?: Theme | CustomTheme = new CustomThemeImpl({});
1832  themeColorMode?: ThemeColorMode = ThemeColorMode.SYSTEM;
1833  @State fontSizeScale: number = 1;
1834  @State minContentHeight: number = MIN_CONTENT_HEIGHT;
1835
1836  build() {
1837    Column() {
1838      CustomDialogContentComponent({
1839        controller: this.controller,
1840        contentBuilder: () => {
1841          this.contentBuilder();
1842        },
1843        theme: this.theme,
1844        themeColorMode: this.themeColorMode,
1845        fontSizeScale: this.fontSizeScale,
1846        minContentHeight: this.minContentHeight,
1847      }).constraintSize({ maxHeight: '100%' });
1848    }
1849  }
1850
1851  @Builder
1852  contentBuilder(): void {
1853    Column() {
1854      Row() {
1855        Text(this.content)
1856          .fontSize(`${BODY_L}fp`)
1857          .fontWeight(FontWeight.Regular)
1858          .fontColor(this.fontColorWithTheme)
1859          .layoutWeight(LOADING_TEXT_LAYOUT_WEIGHT)
1860          .maxLines(this.fontSizeScale > MAX_FONT_SCALE ? LOADING_MAX_LINES_BIG_FONT : LOADING_MAX_LINES)
1861          .focusable(true)
1862          .defaultFocus(true)
1863          .focusBox({
1864            strokeWidth: LengthMetrics.px(0)
1865          })
1866          .textOverflow({ overflow: TextOverflow.Ellipsis })
1867        LoadingProgress()
1868          .color(this.loadingProgressIconColorWithTheme)
1869          .width(LOADING_PROGRESS_WIDTH)
1870          .height(LOADING_PROGRESS_HEIGHT)
1871          .margin({ start: LengthMetrics.vp(LOADING_TEXT_MARGIN_LEFT) })
1872      }
1873      .constraintSize({ minHeight: LOADING_MIN_HEIGHT })
1874    }
1875  }
1876
1877  aboutToAppear(): void {
1878    this.fontColorWithTheme = this.theme?.colors?.fontPrimary ?
1879    this.theme.colors.fontPrimary : $r('sys.color.font_primary');
1880    this.loadingProgressIconColorWithTheme = this.theme?.colors?.iconSecondary ?
1881    this.theme.colors.iconSecondary : $r('sys.color.icon_secondary');
1882  }
1883}
1884
1885@Component
1886export struct PopoverDialog {
1887  @Link visible: boolean;
1888  @Prop popover: PopoverOptions;
1889  @BuilderParam targetBuilder: Callback<void>;
1890  @State dialogWidth: Dimension | undefined = this.popover?.width;
1891
1892  @Builder
1893  emptyBuilder() {
1894  }
1895
1896  aboutToAppear(): void {
1897    if (this.targetBuilder === undefined || this.targetBuilder === null) {
1898      this.targetBuilder = this.emptyBuilder;
1899    }
1900  }
1901
1902  build() {
1903    Column() {
1904      this.targetBuilder();
1905    }
1906    .onClick(() => {
1907      try {
1908        let screenSize: display.Display = display.getDefaultDisplaySync();
1909        let screenWidth: number = px2vp(screenSize.width);
1910        if (screenWidth - BUTTON_HORIZONTAL_MARGIN - BUTTON_HORIZONTAL_MARGIN > MAX_DIALOG_WIDTH) {
1911          this.popover.width = this.popover?.width ?? MAX_DIALOG_WIDTH;
1912        } else {
1913          this.popover.width = this.dialogWidth;
1914        }
1915        this.visible = !this.visible;
1916      } catch (error) {
1917        let code: number = (error as BusinessError).code;
1918        let message: string = (error as BusinessError).message;
1919        hilog.error(0x3900, 'Ace', `dialog popup error, code: ${code}, message: ${message}`);
1920      }
1921    })
1922    .bindPopup(this.visible, {
1923      builder: this.popover?.builder,
1924      placement: this.popover?.placement ?? Placement.Bottom,
1925      popupColor: this.popover?.popupColor,
1926      enableArrow: this.popover?.enableArrow ?? true,
1927      autoCancel: this.popover?.autoCancel,
1928      onStateChange: this.popover?.onStateChange ?? ((e) => {
1929        if (!e.isVisible) {
1930          this.visible = false
1931        }
1932      }),
1933      arrowOffset: this.popover?.arrowOffset,
1934      showInSubWindow: this.popover?.showInSubWindow,
1935      mask: this.popover?.mask,
1936      targetSpace: this.popover?.targetSpace,
1937      offset: this.popover?.offset,
1938      width: this.popover?.width,
1939      arrowPointPosition: this.popover?.arrowPointPosition,
1940      arrowWidth: this.popover?.arrowWidth,
1941      arrowHeight: this.popover?.arrowHeight,
1942      radius: this.popover?.radius ?? $r('sys.float.corner_radius_level16'),
1943      shadow: this.popover?.shadow ?? ShadowStyle.OUTER_DEFAULT_MD,
1944      backgroundBlurStyle: this.popover?.backgroundBlurStyle ?? BlurStyle.COMPONENT_ULTRA_THICK,
1945      focusable: this.popover?.focusable,
1946      transition: this.popover?.transition,
1947      onWillDismiss: this.popover?.onWillDismiss
1948    })
1949  }
1950}
1951
1952export declare interface PopoverOptions extends CustomPopupOptions {}