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