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