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