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