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