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