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