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