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 {}