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