• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2024 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 { Theme } from '@ohos.arkui.theme';
17import { LengthMetrics } from '@ohos.arkui.node';
18import { common, EnvironmentCallback } from '@kit.AbilityKit';
19import { BusinessError } from '@kit.BasicServicesKit';
20import { hilog } from '@kit.PerformanceAnalysisKit';
21import measure from '@ohos.measure';
22
23export enum IconType {
24  BADGE = 1,
25  NORMAL_ICON,
26  SYSTEM_ICON,
27  HEAD_SCULPTURE,
28  APP_ICON,
29  PREVIEW,
30  LONGITUDINAL,
31  VERTICAL
32}
33
34enum FontSizeScaleLevel {
35  LEVEL1 = 1.75,
36  LEVEL2 = 2,
37  LEVEL3 = 3.2
38}
39
40enum ItemHeight {
41  FIRST_HEIGHT = 48,
42  SECOND_HEIGHT = 56,
43  THIRD_HEIGHT = 64,
44  FOURTH_HEIGHT = 72,
45  FIFTH_HEIGHT = 96
46}
47
48export declare class OperateItem {
49  public icon?: OperateIcon;
50  public subIcon?: OperateIcon;
51  public button?: OperateButton;
52  public switch?: OperateCheck;
53  public checkbox?: OperateCheck;
54  public radio?: OperateCheck;
55  public image?: ResourceStr;
56  public symbolStyle?: SymbolGlyphModifier;
57  public text?: ResourceStr;
58  public arrow?: OperateIcon;
59}
60
61export declare class ContentItem {
62  public iconStyle?: IconType;
63  public icon?: ResourceStr;
64  public symbolStyle?: SymbolGlyphModifier;
65  public primaryText?: ResourceStr;
66  public secondaryText?: ResourceStr;
67  public description?: ResourceStr;
68}
69
70export declare class OperateIcon {
71  public value: ResourceStr;
72  public symbolStyle?: SymbolGlyphModifier;
73  public action?: () => void;
74  public accessibilityText?: ResourceStr;
75  public accessibilityDescription?: ResourceStr;
76  public accessibilityLevel?: string;
77}
78
79export declare class OperateButton {
80  public text?: ResourceStr;
81  public accessibilityText?: ResourceStr;
82  public accessibilityDescription?: ResourceStr;
83  public accessibilityLevel?: string;
84}
85
86export declare class OperateCheck {
87  public isCheck?: boolean;
88  public onChange?: (value: boolean) => void;
89  public accessibilityText?: ResourceStr;
90  public accessibilityDescription?: ResourceStr;
91  public accessibilityLevel?: string;
92}
93
94const TEXT_MAX_LINE = 1;
95const ITEM_BORDER_SHOWN = 2;
96const TEXT_COLUMN_SPACE = 4;
97const TEXT_SAFE_MARGIN = 8;
98const LISTITEM_PADDING = 6;
99const SWITCH_PADDING = 4;
100const STACK_PADDING = 4;
101const BADGE_SIZE = 8;
102const SMALL_ICON_SIZE = 16;
103const SYSTEM_ICON_SIZE = 24;
104const TEXT_ARROW_HEIGHT = 32;
105const SAFE_LIST_PADDING = 32;
106const HEADSCULPTURE_SIZE = 40;
107const BUTTON_SIZE = 28;
108const APP_ICON_SIZE = 64;
109const PREVIEW_SIZE = 96;
110const LONGITUDINAL_SIZE = 96;
111const VERTICAL_SIZE = 96;
112const NORMAL_ITEM_ROW_SPACE = 16;
113const SPECIAL_ITEM_ROW_SPACE = 0;
114const SPECIAL_ICON_SIZE = 0;
115const DEFAULT_ROW_SPACE = 0;
116const SPECICAL_ROW_SPACE = 4;
117const OPERATEITEM_ICONLIKE_SIZE = 24;
118const OPERATEITEM_SELECTIONBOX_PADDING_SIZE = 2;
119const OPERATEITEM_ARROW_WIDTH = 12
120const OPERATEITEM_ICON_CLICKABLE_SIZE = 40;
121const OPERATEITEM_IMAGE_SIZE = 48;
122const RIGHT_CONTENT_NULL_RIGHTWIDTH = '0vp';
123const LEFT_PART_WIDTH = 'calc(66% - 16vp)';
124const RIGHT_PART_WIDTH = '34%';
125const RIGHT_ONLY_ARROW_WIDTH = '24vp';
126const RIGHT_ONLY_IMAGE_WIDTH = '54vp';
127const RIGHT_ONLY_ICON_WIDTH = '40vp';
128const RIGHT_ICON_SUB_ICON_WIDTH = '80vp';
129const RIGHT_ONLY_RADIO_WIDTH = '30vp';
130const RIGHT_ONLY_CHECKBOX_WIDTH = '30vp';
131const RIGHT_ONLY_SWITCH_WIDTH = '44vp';
132const ACCESSIBILITY_LEVEL_AUTO = 'auto';
133const ACCESSIBILITY_LEVEL_YES = 'yes';
134const ACCESSIBILITY_LEVEL_NO = 'no';
135const RESOURCE_TYPE_SYMBOL: number = 40000;
136
137const ICON_SIZE_MAP: Map<number, number> = new Map([
138  [IconType.BADGE, BADGE_SIZE],
139  [IconType.NORMAL_ICON, SMALL_ICON_SIZE],
140  [IconType.SYSTEM_ICON, SYSTEM_ICON_SIZE],
141  [IconType.HEAD_SCULPTURE, HEADSCULPTURE_SIZE],
142  [IconType.APP_ICON, APP_ICON_SIZE],
143  [IconType.PREVIEW, PREVIEW_SIZE],
144  [IconType.LONGITUDINAL, LONGITUDINAL_SIZE],
145  [IconType.VERTICAL, VERTICAL_SIZE]
146])
147// Does it support events such as focus, hover, press, etc. for the sub components of list
148const IS_SUPPORT_SUBCOMPONENT_EVENT: boolean =
149  LengthMetrics.resource($r('sys.float.composeListItem_focus_dynamic_effect')).value !== 1;
150const RECOVER_ITEM_SCALE: number = 1;
151const CLEAR_SHADOW: ShadowStyle = -1;
152const OPERATE_ITEM_RADIUS: number = 50;
153const OPERATE_ITEM_BACKGROUND_COLOR: ResourceColor = '#33000000';
154const DEFUALT_RADIO_CHECKBOX_BORDER_COLOR: ResourceColor = $r('sys.color.ohos_id_color_switch_outline_off');
155const OPERATE_ITEM_COLOR: ResourceColor = '#99000000';
156const TEXT_SUPPORT_MARQUEE: number = 1;
157const IS_MARQUEE_OR_ELLIPSIS: number = LengthMetrics.resource($r('sys.float.composeListItem_right_textOverflow')).value;
158const UNUSUAL: number = -1;
159const FOCUSED_BG_COLOR: ResourceColor = $r('sys.color.composeListItem_container_focus_color');
160const NORMAL_BG_COLOR: ResourceColor = $r('sys.color.composeListItem_container_normal_color');
161const FOCUSED_ITEM_SCALE: number = LengthMetrics.resource($r('sys.float.composeListItem_focus_magnification')).value;
162const FOCUSED_SHADOW: ShadowStyle = LengthMetrics.resource($r('sys.float.composeListItem_focus_shadow_attribute'))
163  .value as ShadowStyle;
164const NORMAL_SHADOW: ShadowStyle = LengthMetrics.resource($r('sys.float.composeListItem_normal_shadow_attribute'))
165  .value as ShadowStyle;
166const ITEM_PADDING: Resource = $r('sys.float.composeListItem_padding');
167const OPERATEITEM_ARROW_MARGIN_WIDTH: number = LengthMetrics.resource(
168  $r('sys.float.composeListItem_arrow_margin')).value;
169const APPICON_ITEMLENGTH: number = LengthMetrics.resource(
170  $r('sys.float.composeListItem_AppIcon_ItemLength')).value;
171
172class Util {
173  public static isSymbolResource(resourceStr: ResourceStr | undefined | null): boolean {
174    if (!Util.isResourceType(resourceStr)) {
175      return false;
176    }
177    let resource: Resource = resourceStr as Resource;
178    return resource.type === RESOURCE_TYPE_SYMBOL;
179  }
180
181  public static isResourceType(resource: ResourceStr | Resource | undefined | null): boolean {
182    if (!resource) {
183      return false;
184    }
185    if (typeof resource === 'string' || typeof resource === 'undefined') {
186      return false;
187    }
188    return true;
189  }
190}
191
192@Component
193struct ContentItemStruct {
194  @Prop @Watch('onPropChange') iconStyle: IconType | null = null;
195  @Prop @Watch('onPropChange') icon: ResourceStr | null = null;
196  @Prop @Watch('onPropChange') symbolStyle: SymbolGlyphModifier | null = null;
197  @Prop @Watch('onPropChange') primaryText: ResourceStr | null = null;
198  @Prop @Watch('onPropChange') secondaryText: ResourceStr | null = null;
199  @Prop @Watch('onPropChange') description: ResourceStr | null = null;
200  @State itemRowSpace: number = NORMAL_ITEM_ROW_SPACE;
201  @Prop leftWidth: string = LEFT_PART_WIDTH;
202  @State @Watch('onPropChange') primaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_primary');
203  @State @Watch('onPropChange') secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary');
204  @State @Watch('onPropChange') descriptionColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary');
205  @Prop fontSizeScale: number;
206  @Prop parentDirection: FlexDirection;
207  @Prop itemDirection: FlexDirection;
208  @Prop @Watch('onPropChange') isFocus: boolean = false;
209  @State primaryTextSize: string | number | Resource = $r('sys.float.ohos_id_text_size_body1');
210  @State primaryTextColors: ResourceColor = $r('sys.color.font_primary');
211  @Prop itemHeight: number | null = null;
212  @State iconColor: ResourceColor | null = null;
213  @State secondaryTextColors: ResourceColor = $r('sys.color.font_secondary');
214  @State secondaryThirdTextSize: string | number | Resource =
215    $r('sys.float.composeListItem_left_secondary_tertiary_text_size');
216  @State descriptionColors: ResourceColor = $r('sys.color.font_tertiary');
217  @Link isWrapText: Boolean;
218  @State @Watch('onWrapChange') isWrapFirstText: Boolean = false;
219  @State @Watch('onWrapChange') isWrapSecondText: Boolean = false;
220  @State @Watch('onWrapChange') isWrapThirdText: Boolean = false;
221
222  onWillApplyTheme(theme: Theme): void {
223    this.primaryTextColor = theme.colors.fontPrimary;
224    this.secondaryTextColor = theme.colors.fontSecondary;
225    this.descriptionColor = theme.colors.fontTertiary;
226  }
227
228  onPropChange(): void {
229    if (this.icon == null && this.symbolStyle == null && this.iconStyle == null) {
230      this.itemRowSpace = SPECIAL_ITEM_ROW_SPACE;
231    } else {
232      this.itemRowSpace = NORMAL_ITEM_ROW_SPACE;
233    }
234    if (!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus) {
235      this.primaryTextColors = $r('sys.color.composeListItem_left_text_focus_color');
236      this.secondaryTextColors = $r('sys.color.composeListItem_left_secondary_text_focus_color');
237      this.descriptionColors = $r('sys.color.composeListItem_left_secondary_text_focus_color');
238    } else {
239      this.primaryTextColors = this.primaryTextColor;
240      this.secondaryTextColors = this.secondaryTextColor;
241      this.descriptionColors = this.descriptionColor;
242    }
243  }
244
245  onWrapChange(): void {
246    this.isWrapText = this.isWrapFirstText || this.isWrapSecondText || this.isWrapThirdText;
247  }
248
249  getContentItemIconFillColor(): ResourceColor {
250    switch (this.iconStyle) {
251      case IconType.BADGE:
252        return $r('sys.color.composeListItem_badge_color');
253      case IconType.SYSTEM_ICON:
254        return $r('sys.color.composeListItem_icon_normal_color');
255      default:
256        return $r('sys.color.ohos_id_color_secondary');
257    }
258  }
259
260  judgeIsWrap(text: ResourceStr | null, sizeResource: Length, newHeight: number): boolean {
261    let singleRowHeight = this.getSingleRowTextHeight(text, sizeResource);
262    return newHeight > singleRowHeight;
263  }
264
265  getSingleRowTextHeight(text: ResourceStr | null, sizeResource: Length): number {
266    if (text && sizeResource) {
267      let singleRowHeight = px2vp(measure.measureTextSize({
268        textContent: text,
269        fontSize: sizeResource,
270        maxLines: TEXT_MAX_LINE
271      }).height as number);
272      return singleRowHeight;
273    }
274    return 0;
275  }
276
277  aboutToAppear(): void {
278    this.onPropChange();
279  }
280
281  @Builder
282  createIcon() {
283    if (this.iconStyle != null && ICON_SIZE_MAP.has(this.iconStyle)) {
284      if (this.symbolStyle != null) {
285        SymbolGlyph()
286          .fontColor([this.getContentItemIconFillColor()])
287          .attributeModifier(this.symbolStyle)
288          .fontSize(`${ICON_SIZE_MAP.get(this.iconStyle)}vp`)
289          .effectStrategy(SymbolEffectStrategy.NONE)
290          .symbolEffect(new SymbolEffect(), false)
291          .borderRadius($r('sys.float.composeListItem_Image_Radius'))
292          .focusable(false)
293          .draggable(false)
294          .flexShrink(0)
295      } else if (this.icon != null) {
296        if (Util.isSymbolResource(this.icon)) {
297          SymbolGlyph(this.icon as Resource)
298            .fontSize(`${ICON_SIZE_MAP.get(this.iconStyle)}vp`)
299            .fontColor([this.getContentItemIconFillColor()])
300            .borderRadius($r('sys.float.composeListItem_Image_Radius'))
301            .focusable(false)
302            .draggable(false)
303            .flexShrink(0)
304        } else {
305          if (this.iconStyle <= IconType.PREVIEW) {
306            Image(this.icon)
307              .objectFit(ImageFit.Contain)
308              .width(ICON_SIZE_MAP.get(this.iconStyle))
309              .height(ICON_SIZE_MAP.get(this.iconStyle))
310              .borderRadius($r('sys.float.composeListItem_Image_Radius'))
311              .focusable(false)
312              .draggable(false)
313              .fillColor(this.getContentItemIconFillColor())
314              .flexShrink(0)
315          } else {
316            Image(this.icon)
317              .objectFit(ImageFit.Contain)
318              .constraintSize({
319                minWidth: SPECIAL_ICON_SIZE,
320                maxWidth: ICON_SIZE_MAP.get(this.iconStyle),
321                minHeight: SPECIAL_ICON_SIZE,
322                maxHeight: ICON_SIZE_MAP.get(this.iconStyle)
323              })
324              .borderRadius($r('sys.float.composeListItem_Image_Radius'))
325              .focusable(false)
326              .draggable(false)
327              .fillColor(this.getContentItemIconFillColor())
328              .flexShrink(0)
329          }
330        }
331      }
332    }
333  }
334
335  @Builder
336  createText() {
337    Column({ space: TEXT_COLUMN_SPACE }) {
338      Text(this.primaryText)
339        .fontSize(this.primaryTextSize)
340        .fontColor(this.primaryTextColors)
341        .textOverflow({
342          overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None :
343          TextOverflow.Ellipsis
344        })
345        .fontWeight(FontWeight.Medium)
346        .focusable(true)
347        .draggable(false)
348        .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
349          if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
350            this.isWrapFirstText = this.judgeIsWrap(this.primaryText, this.primaryTextSize,
351              newValue.height as number);
352          }
353        })
354      if (this.secondaryText != null) {
355        Text(this.secondaryText)
356          .fontSize(this.secondaryThirdTextSize)
357          .fontColor(this.secondaryTextColors)
358          .textOverflow({
359            overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None :
360            TextOverflow.Ellipsis
361          })
362          .draggable(false)
363          .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
364            if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
365              this.isWrapSecondText = this.judgeIsWrap(this.secondaryText, this.secondaryThirdTextSize,
366                newValue.height as number);
367            }
368          })
369      }
370      if (this.description != null) {
371        Text(this.description)
372          .fontSize(this.secondaryThirdTextSize)
373          .fontColor(this.descriptionColors)
374          .textOverflow({
375            overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.None :
376            TextOverflow.Ellipsis
377          })
378          .draggable(false)
379          .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
380            if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
381              this.isWrapThirdText = this.judgeIsWrap(this.description, this.secondaryThirdTextSize,
382                newValue.height as number);
383            }
384          })
385      }
386    }
387    .flexShrink(1)
388    .margin(this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 ? undefined : {
389      top: TEXT_SAFE_MARGIN,
390      bottom: TEXT_SAFE_MARGIN
391    })
392    .alignItems(HorizontalAlign.Start)
393  }
394
395  isColumnDirection(): boolean {
396    return this.itemDirection === FlexDirection.Column;
397  }
398
399  isParentColumnDirection(): boolean {
400    return this.parentDirection === FlexDirection.Column;
401  }
402
403  getItemSpace() {
404    if (this.isColumnDirection()) {
405      return LengthMetrics.resource($r('sys.float.padding_level1'));
406    }
407    return LengthMetrics.vp(this.itemRowSpace);
408  }
409
410  build() {
411    Flex({
412      space: { main: this.getItemSpace() },
413      direction: this.itemDirection,
414      justifyContent: FlexAlign.Start,
415      alignItems: this.isColumnDirection() ? ItemAlign.Start : ItemAlign.Center,
416    }) {
417      this.createIcon();
418      this.createText();
419    }
420    .height(this.itemDirection === FlexDirection.Column ? 'auto' : undefined)
421    .margin({
422      end: this.isParentColumnDirection() ?
423      LengthMetrics.vp(0) :
424      LengthMetrics.vp(16)
425    })
426    .padding({ start: LengthMetrics.vp(LISTITEM_PADDING) })
427    .flexShrink(this.isParentColumnDirection() ? 0 : 1)
428  }
429}
430
431class CreateIconParam {
432  public icon?: OperateIcon;
433}
434
435@Component
436struct OperateItemStruct {
437  @Prop @Watch('onPropChange') arrow: OperateIcon | null = null;
438  @Prop @Watch('onPropChange') icon: OperateIcon | null = null;
439  @Prop @Watch('onPropChange') subIcon: OperateIcon | null = null;
440  @Prop @Watch('onPropChange') button: OperateButton | null = null;
441  @Prop @Watch('onPropChange') switch: OperateCheck | null = null;
442  @Prop @Watch('onPropChange') checkBox: OperateCheck | null = null;
443  @Prop @Watch('onPropChange') radio: OperateCheck | null = null;
444  @Prop @Watch('onPropChange') image: ResourceStr | null = null;
445  @Prop @Watch('onPropChange') symbolStyle: SymbolGlyphModifier | null = null;
446  @Prop @Watch('onPropChange') text: ResourceStr | null = null;
447  @State switchState: boolean = false;
448  @State radioState: boolean = false;
449  @State checkBoxState: boolean = false;
450  @Prop rightWidth: string = RIGHT_PART_WIDTH;
451  @State @Watch('onFocusChange') secondaryTextColor: ResourceColor = $r('sys.color.ohos_id_color_text_secondary');
452  @State hoveringColor: ResourceColor = '#0d000000';
453  @State activedColor: ResourceColor = '#1a0a59f7';
454  @Link parentCanFocus: boolean;
455  @Link parentCanTouch: boolean;
456  @Link parentIsHover: boolean;
457  @Link parentCanHover: boolean;
458  @Link parentIsActive: boolean;
459  @Link parentFrontColor: ResourceColor;
460  @Link parentDirection: FlexDirection;
461  @State rowSpace: number = DEFAULT_ROW_SPACE;
462  @Link @Watch('onFocusChange') isFocus: boolean;
463  @State secondaryTextSize: Length = $r('sys.float.ohos_id_text_size_body2');
464  @State secondaryTextColors: ResourceColor = $r('sys.color.font_secondary');
465  @State iconColor: ResourceColor = $r('sys.color.composeListItem_right_icon_normal_color');
466  @Link @Watch('onPropChange') isChecked: boolean;
467
468  onWillApplyTheme(theme: Theme): void {
469    this.secondaryTextColor = theme.colors.fontSecondary;
470    this.hoveringColor = theme.colors.interactiveHover;
471    this.activedColor = theme.colors.interactiveActive;
472  }
473
474  onFocusChange() {
475    if (!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus) {
476      this.secondaryTextColors = $r('sys.color.composeListItem_right_text_focus_color');
477    } else {
478      this.secondaryTextColors = this.secondaryTextColor;
479    }
480    this.iconColor = this.isFocus ? $r('sys.color.composeListItem_right_icon_focus_color') :
481    $r('sys.color.composeListItem_right_icon_normal_color');
482  }
483
484  onPropChange(): void {
485    if (this.switch != null) {
486      this.switchState = IS_SUPPORT_SUBCOMPONENT_EVENT ? this.switch.isCheck as boolean : this.isChecked;
487    }
488    if (this.radio != null) {
489      this.radioState = IS_SUPPORT_SUBCOMPONENT_EVENT ? this.radio.isCheck as boolean : this.isChecked;
490    }
491    if (this.checkBox != null) {
492      this.checkBoxState = IS_SUPPORT_SUBCOMPONENT_EVENT ? this.checkBox.isCheck as boolean : this.isChecked;
493    }
494
495    if ((this.button == null && this.image == null && this.symbolStyle == null && this.text != null) &&
496      ((this.icon != null) || (this.icon == null && this.arrow != null))) {
497      this.rowSpace = SPECICAL_ROW_SPACE;
498    } else {
499      this.rowSpace = DEFAULT_ROW_SPACE;
500    }
501  }
502
503  getUnselectedColor(): ResourceColor {
504    if (IS_SUPPORT_SUBCOMPONENT_EVENT) {
505      return DEFUALT_RADIO_CHECKBOX_BORDER_COLOR;
506    }
507    return this.isFocus ? OPERATE_ITEM_COLOR : DEFUALT_RADIO_CHECKBOX_BORDER_COLOR;
508  }
509
510  aboutToAppear(): void {
511    if (this.switch !== null) {
512      this.isChecked = this.switch.isCheck as boolean;
513    }
514    if (this.radio !== null) {
515      this.isChecked = this.radio.isCheck as boolean;
516    }
517    if (this.checkBox !== null) {
518      this.isChecked = this.checkBox.isCheck as boolean;
519    }
520    this.onPropChange();
521    this.onFocusChange();
522  }
523
524  @Builder
525  createButton() {
526    Button() {
527      Row() {
528        Text(this.button?.text as ResourceStr)
529          .focusable(true)
530      }
531      .padding({
532        left: TEXT_SAFE_MARGIN,
533        right: TEXT_SAFE_MARGIN
534      })
535    }
536    .padding({ top: 0, bottom: 0 })
537    .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
538    .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None)
539    .fontSize($r('sys.float.ohos_id_text_size_button3'))
540    .fontColor($r('sys.color.ohos_id_color_text_primary_activated_transparent'))
541    .constraintSize({
542      minHeight: BUTTON_SIZE
543    })
544    .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
545    .labelStyle({
546      maxLines: TEXT_MAX_LINE
547    })
548    .onFocus(() => {
549      this.parentCanFocus = false;
550    })
551    .onHover((isHover: boolean) => {
552      this.parentCanHover = false;
553      if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) {
554        this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
555      }
556      if (!isHover) {
557        this.parentCanHover = true;
558        if (this.parentIsHover) {
559          this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
560            (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
561        }
562      }
563    })
564    .accessibilityLevel(this.button?.accessibilityLevel ?? ACCESSIBILITY_LEVEL_AUTO)
565    .accessibilityText(getAccessibilityText(this.button?.accessibilityText ?? ''))
566    .accessibilityDescription(getAccessibilityText(this.button?.accessibilityDescription ?? ''))
567  }
568
569  @Builder
570  createIcon(param: CreateIconParam) {
571    Button({ type: ButtonType.Normal }) {
572      if (param.icon?.symbolStyle) {
573        SymbolGlyph()
574          .fontColor([this.iconColor])
575          .attributeModifier(param.icon?.symbolStyle)
576          .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
577          .effectStrategy(SymbolEffectStrategy.NONE)
578          .symbolEffect(new SymbolEffect(), false)
579          .focusable(true)
580          .draggable(false)
581      } else {
582        if (Util.isSymbolResource(param.icon?.value)) {
583          SymbolGlyph(param.icon?.value as Resource)
584            .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
585            .fontColor([this.iconColor])
586            .focusable(true)
587            .draggable(false)
588        } else {
589          Image(param.icon?.value)
590            .height(OPERATEITEM_ICONLIKE_SIZE)
591            .width(OPERATEITEM_ICONLIKE_SIZE)
592            .focusable(true)
593            .fillColor(this.iconColor)
594            .draggable(false)
595        }
596      }
597    }
598    .shadow(CLEAR_SHADOW)
599    .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None)
600    .backgroundColor(Color.Transparent)
601    .height(OPERATEITEM_ICON_CLICKABLE_SIZE)
602    .width(OPERATEITEM_ICON_CLICKABLE_SIZE)
603    .borderRadius($r('sys.float.ohos_id_corner_radius_clicked'))
604    .onFocus(() => {
605      this.parentCanFocus = false;
606    })
607    .onHover((isHover: boolean) => {
608      this.parentCanHover = false;
609      if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) {
610        this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
611      }
612      if (!isHover) {
613        this.parentCanHover = true;
614        if (this.parentIsHover) {
615          this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
616            (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
617        }
618      }
619    })
620    .onClick(param.icon?.action)
621    .accessibilityLevel(getAccessibilityLevelOnAction(param.icon?.accessibilityLevel, param.icon?.action))
622    .accessibilityText(getAccessibilityText(param.icon?.accessibilityText ?? ''))
623    .accessibilityDescription(getAccessibilityText(param.icon?.accessibilityDescription ?? ''))
624    .flexShrink(0)
625  }
626
627  @Builder
628  createImage() {
629    if (Util.isSymbolResource(this.image)) {
630      SymbolGlyph(this.image as Resource)
631        .fontSize(`${OPERATEITEM_IMAGE_SIZE}vp`)
632        .draggable(false)
633        .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
634    } else {
635      Image(this.image)
636        .height(OPERATEITEM_IMAGE_SIZE)
637        .width(OPERATEITEM_IMAGE_SIZE)
638        .draggable(false)
639        .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
640    }
641  }
642
643  @Builder
644  createSymbol() {
645    SymbolGlyph()
646      .attributeModifier(this.symbolStyle)
647      .fontSize(`${OPERATEITEM_IMAGE_SIZE}vp`)
648      .effectStrategy(SymbolEffectStrategy.NONE)
649      .symbolEffect(new SymbolEffect(), false)
650      .draggable(false)
651      .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
652  }
653
654  @Builder
655  createText() {
656    Text(this.text)
657      .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
658      .fontSize(this.secondaryTextSize)
659      .fontColor(this.secondaryTextColors)
660      .textOverflow({
661        overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.MARQUEE :
662        TextOverflow.None
663      })
664      .marqueeOptions({
665        start: this.isFocus || this.parentIsHover,
666        fadeout: true,
667        marqueeStartPolicy: MarqueeStartPolicy.DEFAULT
668      })
669      .maxLines(LengthMetrics.resource($r('sys.float.composeListItem_maxLines_right')).value)
670      .draggable(false)
671      .flexShrink(1)
672  }
673
674  @Builder
675  createArrow() {
676    Button({ type: ButtonType.Normal }) {
677      if (this.arrow?.symbolStyle) {
678        SymbolGlyph()
679          .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor])
680          .attributeModifier(this.arrow?.symbolStyle)
681          .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
682          .effectStrategy(SymbolEffectStrategy.NONE)
683          .symbolEffect(new SymbolEffect(), false)
684          .focusable(true)
685          .draggable(false)
686      } else {
687        if (Util.isSymbolResource(this.arrow?.value)) {
688          SymbolGlyph(this.arrow?.value as Resource)
689            .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
690            .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor])
691            .focusable(true)
692            .draggable(false)
693        } else {
694          Image(this.arrow?.value)
695            .height(OPERATEITEM_ICONLIKE_SIZE)
696            .width(OPERATEITEM_ARROW_WIDTH)
697            .focusable(true)
698            .fillColor(IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.ohos_id_color_fourth') : this.iconColor)
699            .draggable(false)
700            .matchTextDirection(true)
701        }
702      }
703    }
704    .shadow(CLEAR_SHADOW)
705    .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
706    .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ?
707      (this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) : HitTestMode.None)
708    .backgroundColor(Color.Transparent)
709    .height(OPERATEITEM_ICONLIKE_SIZE)
710    .width(OPERATEITEM_ARROW_WIDTH)
711    .onFocus(() => {
712      this.parentCanFocus = false;
713    })
714    .stateEffect(this.arrow?.action !== undefined)
715    .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None)
716    .onHover((isHover: boolean) => {
717      if (this.arrow?.action === undefined) {
718        return;
719      }
720      if (isHover && IS_SUPPORT_SUBCOMPONENT_EVENT) {
721        this.parentCanHover = false;
722        this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
723      } else {
724        this.parentCanHover = true;
725        if (this.parentIsHover) {
726          this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
727            (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
728        }
729      }
730    })
731    .onClick(this.arrow?.action)
732    .accessibilityLevel(getAccessibilityLevelOnAction(this.arrow?.accessibilityLevel, this.arrow?.action))
733    .accessibilityText(getAccessibilityText(this.arrow?.accessibilityText ?? ''))
734    .accessibilityDescription(getAccessibilityText(this.arrow?.accessibilityDescription ?? ''))
735  }
736
737  @Builder
738  createRadio() {
739    Radio({ value: '', group: '' })
740      .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
741      .checked(this.radioState)
742      .radioStyle({
743        uncheckedBorderColor: this.getUnselectedColor()
744      })
745      .backgroundColor(!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus ? OPERATE_ITEM_BACKGROUND_COLOR :
746      Color.Transparent)
747      .borderRadius(OPERATE_ITEM_RADIUS)
748      .onChange((isCheck: boolean) => {
749        if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
750          this.radioState = isCheck;
751          this.isChecked = isCheck;
752        }
753        if (this.radio?.onChange) {
754          this.radio?.onChange(isCheck);
755        }
756      })
757      .height(OPERATEITEM_ICONLIKE_SIZE)
758      .width(OPERATEITEM_ICONLIKE_SIZE)
759      .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE)
760      .onFocus(() => {
761        this.parentCanFocus = false;
762      })
763      .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None)
764      .flexShrink(0)
765      .onHover((isHover: boolean) => {
766        this.parentCanHover = false;
767        if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) {
768          this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
769        }
770        if (!isHover) {
771          this.parentCanHover = true;
772          if (this.parentIsHover) {
773            this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
774              (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
775          }
776        }
777      })
778      .accessibilityLevel(getAccessibilityLevelOnChange(this.radio?.accessibilityLevel, this.radio?.onChange))
779      .accessibilityText(getAccessibilityText(this.radio?.accessibilityText ?? ''))
780      .accessibilityDescription(getAccessibilityText(this.radio?.accessibilityDescription ?? ''))
781  }
782
783  @Builder
784  createCheckBox() {
785    Checkbox()
786      .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? UNUSUAL : OPERATE_ITEM_RADIUS)
787      .unselectedColor(this.getUnselectedColor())
788      .backgroundColor(!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus ? OPERATE_ITEM_BACKGROUND_COLOR :
789      Color.Transparent)
790      .margin({ end: LengthMetrics.vp(LISTITEM_PADDING) })
791      .select(this.checkBoxState)
792      .onChange((isCheck: boolean) => {
793        if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
794          this.checkBoxState = isCheck;
795          this.isChecked = isCheck;
796        }
797        if (this.checkBox?.onChange) {
798          this.checkBox?.onChange(isCheck);
799        }
800      })
801      .height(OPERATEITEM_ICONLIKE_SIZE)
802      .width(OPERATEITEM_ICONLIKE_SIZE)
803      .padding(OPERATEITEM_SELECTIONBOX_PADDING_SIZE)
804      .onFocus(() => {
805        this.parentCanFocus = false;
806      })
807      .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None)
808      .flexShrink(0)
809      .onHover((isHover: boolean) => {
810        this.parentCanHover = false;
811        if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) {
812          this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
813        }
814        if (!isHover) {
815          this.parentCanHover = true;
816          if (this.parentIsHover) {
817            this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
818              (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
819          }
820        }
821      })
822      .accessibilityLevel(getAccessibilityLevelOnChange(this.checkBox?.accessibilityLevel, this.checkBox?.onChange))
823      .accessibilityText(getAccessibilityText(this.checkBox?.accessibilityText ?? ''))
824      .accessibilityDescription(getAccessibilityText(this.checkBox?.accessibilityDescription ?? ''))
825  }
826
827  @Builder
828  createSwitch() {
829    Row() {
830      Toggle({ type: ToggleType.Switch, isOn: this.switchState })
831        .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? UNUSUAL : OPERATE_ITEM_RADIUS)
832        .backgroundColor(!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus ? OPERATE_ITEM_BACKGROUND_COLOR :
833        Color.Transparent)
834        .switchPointColor(!IS_SUPPORT_SUBCOMPONENT_EVENT && this.isFocus && !this.switchState ? OPERATE_ITEM_COLOR :
835          UNUSUAL)
836        .onChange((isCheck: boolean) => {
837          this.switchState = isCheck;
838          if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
839            this.isChecked = isCheck;
840          }
841          if (this.switch?.onChange) {
842            this.switch?.onChange(isCheck);
843          }
844        })
845        .onClick(() => {
846          this.switchState = !this.switchState;
847        })
848        .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ? HitTestMode.Block : HitTestMode.None)
849        .accessibilityLevel(getAccessibilityLevelOnChange(this.switch?.accessibilityLevel, this.switch?.onChange))
850        .accessibilityText(getAccessibilityText(this.switch?.accessibilityText ?? ''))
851        .accessibilityDescription(getAccessibilityText(this.switch?.accessibilityDescription ?? ''))
852    }
853    .margin({ end: LengthMetrics.vp(SWITCH_PADDING) })
854    .height(OPERATEITEM_ICON_CLICKABLE_SIZE)
855    .width(OPERATEITEM_ICON_CLICKABLE_SIZE)
856    .justifyContent(FlexAlign.Center)
857    .onFocus(() => {
858      this.parentCanFocus = false;
859    })
860    .onHover((isHover: boolean) => {
861      this.parentCanHover = false;
862      if (isHover && this.parentFrontColor === this.hoveringColor && IS_SUPPORT_SUBCOMPONENT_EVENT) {
863        this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
864      }
865      if (!isHover) {
866        this.parentCanHover = true;
867        if (this.parentIsHover) {
868          this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
869            (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
870        }
871      }
872    })
873  }
874
875  @Builder
876  createTextArrow() {
877    Button({ type: ButtonType.Normal }) {
878      if (this.parentDirection === FlexDirection.Column) {
879        Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
880          Text(this.text)
881            .fontSize($r('sys.float.ohos_id_text_size_body2'))
882            .fontColor(this.secondaryTextColor)
883            .focusable(true)
884            .draggable(false)
885            .constraintSize({
886              maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH}vp)`
887            })
888          if (this.arrow?.symbolStyle) {
889            SymbolGlyph()
890              .fontColor([$r('sys.color.ohos_id_color_fourth')])
891              .attributeModifier(this.arrow?.symbolStyle)
892              .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
893              .effectStrategy(SymbolEffectStrategy.NONE)
894              .symbolEffect(new SymbolEffect(), false)
895              .focusable(false)
896              .draggable(false)
897          } else {
898            if (Util.isSymbolResource(this.arrow?.value)) {
899              SymbolGlyph(this.arrow?.value as Resource)
900                .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
901                .fontColor([$r('sys.color.ohos_id_color_fourth')])
902                .focusable(false)
903                .draggable(false)
904            } else {
905              Image(this.arrow?.value)
906                .height(OPERATEITEM_ICONLIKE_SIZE)
907                .width(OPERATEITEM_ARROW_WIDTH)
908                .fillColor($r('sys.color.ohos_id_color_fourth'))
909                .focusable(false)
910                .draggable(false)
911                .matchTextDirection(true)
912            }
913          }
914        }
915        .padding({
916          start: LengthMetrics.vp(TEXT_SAFE_MARGIN),
917          end: LengthMetrics.vp(LISTITEM_PADDING)
918        })
919      } else {
920        Row({ space: SPECICAL_ROW_SPACE }) {
921          Text(this.text)
922            .fontSize(this.secondaryTextSize)
923            .fontColor(this.secondaryTextColors)
924            .textOverflow({
925              overflow: IS_MARQUEE_OR_ELLIPSIS === TEXT_SUPPORT_MARQUEE ? TextOverflow.MARQUEE :
926              TextOverflow.None
927            })
928            .marqueeOptions({
929              start: this.isFocus || this.parentIsHover,
930              fadeout: true,
931              marqueeStartPolicy: MarqueeStartPolicy.DEFAULT
932            })
933            .maxLines(LengthMetrics.resource($r('sys.float.composeListItem_maxLines_right')).value)
934            .focusable(true)
935            .draggable(false)
936            .constraintSize({
937              maxWidth: `calc(100% - ${OPERATEITEM_ARROW_WIDTH + OPERATEITEM_ARROW_MARGIN_WIDTH}vp)`
938            })
939            .margin({ right: OPERATEITEM_ARROW_MARGIN_WIDTH })
940          if (this.arrow?.symbolStyle) {
941            SymbolGlyph()
942              .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor])
943              .attributeModifier(this.arrow?.symbolStyle)
944              .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
945              .effectStrategy(SymbolEffectStrategy.NONE)
946              .symbolEffect(new SymbolEffect(), false)
947              .focusable(false)
948              .draggable(false)
949          } else {
950            if (Util.isSymbolResource(this.arrow?.value)) {
951              SymbolGlyph(this.arrow?.value as Resource)
952                .fontSize(`${OPERATEITEM_ICONLIKE_SIZE}vp`)
953                .fontColor([IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor])
954                .focusable(false)
955                .draggable(false)
956            } else {
957              Image(this.arrow?.value)
958                .height(OPERATEITEM_ICONLIKE_SIZE)
959                .width(OPERATEITEM_ARROW_WIDTH)
960                .fillColor(IS_SUPPORT_SUBCOMPONENT_EVENT ? $r('sys.color.icon_fourth') : this.iconColor)
961                .focusable(false)
962                .draggable(false)
963                .matchTextDirection(true)
964            }
965          }
966        }
967        .padding({
968          start: LengthMetrics.vp(TEXT_SAFE_MARGIN),
969          end: LengthMetrics.vp(LISTITEM_PADDING)
970        })
971      }
972    }
973    .shadow(CLEAR_SHADOW)
974    .hitTestBehavior(IS_SUPPORT_SUBCOMPONENT_EVENT ?
975      (this.arrow?.action !== undefined ? HitTestMode.Block : HitTestMode.Transparent) : HitTestMode.None)
976    .labelStyle({
977      maxLines: TEXT_MAX_LINE
978    })
979    .backgroundColor(Color.Transparent)
980    .constraintSize({ minHeight: TEXT_ARROW_HEIGHT })
981    .borderRadius($r('sys.float.ohos_id_corner_radius_clicked'))
982    .onFocus(() => {
983      this.parentCanFocus = false;
984    })
985    .padding({
986      top: 0,
987      bottom: 0,
988      left: 0,
989      right: 0
990    })
991    .stateEffect(this.arrow?.action !== undefined)
992    .hoverEffect(this.arrow?.action !== undefined ? HoverEffect.Auto : HoverEffect.None)
993    .onHover((isHover: boolean) => {
994      if (this.arrow?.action === undefined) {
995        return;
996      }
997      if (isHover && IS_SUPPORT_SUBCOMPONENT_EVENT) {
998        this.parentCanHover = false;
999        this.parentFrontColor = this.parentIsActive ? this.activedColor : Color.Transparent.toString();
1000      } else {
1001        this.parentCanHover = true;
1002        if (this.parentIsHover) {
1003          this.parentFrontColor = this.parentIsHover ? this.hoveringColor :
1004            (this.parentIsActive ? this.activedColor : Color.Transparent.toString());
1005        }
1006      }
1007    })
1008    .onClick(this.arrow?.action)
1009    .accessibilityLevel(getAccessibilityLevelOnAction(this.arrow?.accessibilityLevel, this.arrow?.action))
1010    .accessibilityText(`${this.text} ${getAccessibilityText(this.arrow?.accessibilityText ?? '')}`)
1011    .accessibilityDescription(getAccessibilityText(this.arrow?.accessibilityDescription ?? ''))
1012  }
1013
1014  getFlexOptions(): FlexOptions {
1015    let flexOptions: FlexOptions = { alignItems: ItemAlign.Center };
1016    if (this.parentDirection === FlexDirection.Column) {
1017      flexOptions.justifyContent = FlexAlign.SpaceBetween;
1018    } else {
1019      flexOptions.space = { main: LengthMetrics.vp(this.rowSpace) };
1020      flexOptions.justifyContent = FlexAlign.End;
1021    }
1022    return flexOptions;
1023  }
1024
1025  build() {
1026    Flex(this.getFlexOptions()) {
1027      if (this.button != null) {
1028        this.createButton();
1029      } else if (this.symbolStyle != null) {
1030        this.createSymbol();
1031      } else if (this.image != null) {
1032        this.createImage();
1033      } else if (this.icon != null && this.text != null) {
1034        this.createText();
1035        this.createIcon({ icon: this.icon })
1036      } else if (this.arrow != null && this.text == null) {
1037        this.createArrow();
1038      } else if (this.arrow != null && this.text != null) {
1039        this.createTextArrow();
1040      } else if (this.text != null) {
1041        this.createText();
1042      } else if (this.radio != null) {
1043        this.createRadio();
1044      } else if (this.checkBox != null) {
1045        this.createCheckBox();
1046      } else if (this.switch != null) {
1047        this.createSwitch();
1048      } else if (this.icon != null) {
1049        this.createIcon({ icon: this.icon });
1050        if (this.subIcon != null) {
1051          this.createIcon({ icon: this.subIcon });
1052        }
1053      }
1054    }
1055    .width(this.parentDirection === FlexDirection.Column ? undefined : this.rightWidth)
1056  }
1057}
1058
1059/**
1060 * Obtain accessible text
1061 *
1062 * @param resource initial resource
1063 * @param selected select state
1064 * @returns string
1065 */
1066function getAccessibilityText(resource: ResourceStr): string {
1067  try {
1068    let resourceString: string = '';
1069    if (typeof resource === 'string') {
1070      resourceString = resource;
1071    } else {
1072      resourceString = getContext().resourceManager.getStringSync(resource);
1073    }
1074    return resourceString;
1075  } catch (error) {
1076    let code: number = (error as BusinessError).code;
1077    let message: string = (error as BusinessError).message;
1078    hilog.error(0x3900, 'Ace', `getAccessibilityText error, code: ${code}, message: ${message}`);
1079    return '';
1080  }
1081}
1082
1083/**
1084 * Obtain accessible level
1085 *
1086 * @param resource
1087 * @param selected select state
1088 * @returns string
1089 */
1090function getAccessibilityLevelOnChange(accessibilityLevel?: string, onChange?: (value: boolean) => void): string {
1091  if (accessibilityLevel) {
1092    return accessibilityLevel;
1093  }
1094  if (onChange) {
1095    return ACCESSIBILITY_LEVEL_YES;
1096  }
1097  return ACCESSIBILITY_LEVEL_NO;
1098}
1099
1100/**
1101 * Obtain accessible level
1102 *
1103 * @param resource
1104 * @param selected select state
1105 * @returns string
1106 */
1107function getAccessibilityLevelOnAction(accessibilityLevel?: string, onAction?: () => void): string {
1108  if (accessibilityLevel) {
1109    return accessibilityLevel;
1110  }
1111  if (onAction) {
1112    return ACCESSIBILITY_LEVEL_YES;
1113  }
1114  return ACCESSIBILITY_LEVEL_NO;
1115}
1116
1117@Component
1118export struct ComposeListItem {
1119  @Prop @Watch('onPropChange') contentItem: ContentItem | null = null;
1120  @Prop @Watch('onPropChange') operateItem: OperateItem | null = null;
1121  @State frontColor: ResourceColor = NORMAL_BG_COLOR;
1122  @State borderSize: number = 0;
1123  @State canFocus: boolean = false;
1124  @State canTouch: boolean = true;
1125  @State canHover: boolean = true;
1126  @State isHover: boolean = false;
1127  @State itemHeight: number = ItemHeight.FIRST_HEIGHT;
1128  @State isActive: boolean = false;
1129  @State hoveringColor: ResourceColor = '#0d000000';
1130  @State touchDownColor: ResourceColor = '#1a000000';
1131  @State activedColor: ResourceColor = '#1a0a59f7';
1132  @State focusOutlineColor: ResourceColor = $r('sys.color.ohos_id_color_focused_outline');
1133  @State @Watch('onFontSizeScaleChange') fontSizeScale: number = 1;
1134  @State containerDirection: FlexDirection = FlexDirection.Row;
1135  @State contentItemDirection: FlexDirection = FlexDirection.Row;
1136  @State containerPadding?: Padding | LocalizedPadding | Length = undefined;
1137  @State textArrowLeftSafeOffset: number = 0;
1138  private isFollowingSystemFontScale = this.getUIContext().isFollowingSystemFontScale();
1139  private maxFontScale = this.getUIContext().getMaxFontScale();
1140  private callbackId: number | undefined = undefined;
1141  @State accessibilityTextBuilder: string = '';
1142  @State isFocus: boolean = false;
1143  @State isChecked: boolean = false;
1144  @State @Watch('onWrapChange') isWrapText: boolean = false;
1145
1146  onWillApplyTheme(theme: Theme): void {
1147    this.hoveringColor = theme.colors.interactiveHover;
1148    this.touchDownColor = theme.colors.interactivePressed;
1149    this.activedColor = theme.colors.interactiveActive;
1150    this.focusOutlineColor = theme.colors.interactiveFocus;
1151  }
1152
1153  onWrapChange(): void {
1154    this.containerPadding = this.getPadding();
1155  }
1156
1157  onPropChange(): void {
1158    this.containerDirection = this.decideContainerDirection();
1159    this.contentItemDirection = this.decideContentItemDirection();
1160    if (this.contentItem === undefined) {
1161      if (this.operateItem?.image !== undefined ||
1162        this.operateItem?.symbolStyle !== undefined ||
1163        this.operateItem?.icon !== undefined ||
1164        this.operateItem?.subIcon !== undefined) {
1165        this.itemHeight = OPERATEITEM_IMAGE_SIZE + SAFE_LIST_PADDING;
1166      }
1167      return;
1168    }
1169    if (this.contentItem?.secondaryText === undefined && this.contentItem?.description === undefined) {
1170      if (this.contentItem?.icon === undefined) {
1171        this.itemHeight = ItemHeight.FIRST_HEIGHT;
1172      } else {
1173        this.itemHeight = this.contentItem.iconStyle as number <= IconType.HEAD_SCULPTURE ?
1174        ItemHeight.SECOND_HEIGHT :
1175          (LengthMetrics.resource($r('sys.float.composeListItem_system_icon_line_height')).value);
1176      }
1177    } else if (this.contentItem.description === undefined) {
1178      let iconStyle = this.contentItem.iconStyle as number;
1179      if (this.contentItem.icon === undefined ||
1180        (this.contentItem.icon !== undefined && iconStyle <= IconType.SYSTEM_ICON)) {
1181        this.itemHeight = ItemHeight.THIRD_HEIGHT;
1182      } else {
1183        this.itemHeight = iconStyle === IconType.HEAD_SCULPTURE ? ItemHeight.FOURTH_HEIGHT : APPICON_ITEMLENGTH;
1184      }
1185    } else {
1186      this.itemHeight = ItemHeight.FIFTH_HEIGHT;
1187    }
1188    if (ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number >= this.itemHeight) {
1189      this.itemHeight = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number) as number + SAFE_LIST_PADDING;
1190    }
1191
1192    if (this.operateItem?.arrow && this.operateItem?.text && this.operateItem?.arrow?.action) {
1193      this.accessibilityTextBuilder = `
1194        ${getAccessibilityText(this.contentItem?.primaryText ?? '')}
1195        ${getAccessibilityText(this.contentItem?.secondaryText ?? '')}
1196        ${getAccessibilityText(this.contentItem?.description ?? '')}
1197      `;
1198    } else {
1199      this.accessibilityTextBuilder = `
1200        ${getAccessibilityText(this.contentItem?.primaryText ?? '')}
1201        ${getAccessibilityText(this.contentItem?.secondaryText ?? '')}
1202        ${getAccessibilityText(this.contentItem?.description ?? '')}
1203        ${getAccessibilityText(this.operateItem?.text ?? '')}
1204      `;
1205    }
1206  }
1207
1208  aboutToAppear(): void {
1209    this.fontSizeScale = this.decideFontSizeScale();
1210    this.onPropChange();
1211    try {
1212      this.callbackId = getContext()?.getApplicationContext()?.on('environment', this.envCallback);
1213    } catch (paramError) {
1214      let code = (paramError as BusinessError).code;
1215      let message = (paramError as BusinessError).message;
1216      hilog.error(0x3900, 'Ace',
1217        `ComposeListItem Faild to get environment param error: ${code}, ${message}`);
1218    }
1219    if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
1220      this.onFontSizeScaleChange();
1221    }
1222  }
1223
1224  private envCallback: EnvironmentCallback = {
1225    onConfigurationUpdated: (config) => {
1226      if (config === undefined || !this.isFollowingSystemFontScale) {
1227        this.fontSizeScale = 1;
1228        return;
1229      }
1230      try {
1231        this.fontSizeScale = Math.min(
1232          this.maxFontScale, config.fontSizeScale ?? 1);
1233      } catch (paramError) {
1234        let code = (paramError as BusinessError).code;
1235        let message = (paramError as BusinessError).message;
1236        hilog.error(0x3900, 'Ace',
1237          `ComposeListItem environmentCallback error: ${code}, ${message}`);
1238      }
1239    },
1240    onMemoryLevel: (level) => {
1241    }
1242  }
1243
1244  aboutToDisappear(): void {
1245    if (this.callbackId) {
1246      this.getUIContext()
1247      ?.getHostContext()
1248      ?.getApplicationContext()
1249      ?.off('environment', this.callbackId);
1250      this.callbackId = void (0);
1251    }
1252  }
1253
1254  calculatedRightWidth(): string {
1255    if (this.operateItem?.text || this.operateItem?.button) {
1256      return RIGHT_PART_WIDTH;
1257    }
1258    if (this.operateItem?.switch) {
1259      return RIGHT_ONLY_SWITCH_WIDTH;
1260    } else if (this.operateItem?.checkbox) {
1261      return RIGHT_ONLY_CHECKBOX_WIDTH;
1262    } else if (this.operateItem?.radio) {
1263      return RIGHT_ONLY_RADIO_WIDTH;
1264    } else if (this.operateItem?.icon) {
1265      if (this.operateItem?.subIcon) {
1266        return RIGHT_ICON_SUB_ICON_WIDTH;
1267      }
1268      return RIGHT_ONLY_ICON_WIDTH;
1269    } else if (this.operateItem?.symbolStyle) {
1270      return RIGHT_ONLY_IMAGE_WIDTH;
1271    } else if (this.operateItem?.image) {
1272      return RIGHT_ONLY_IMAGE_WIDTH;
1273    } else if (this.operateItem?.arrow) {
1274      return RIGHT_ONLY_ARROW_WIDTH;
1275    }
1276    return RIGHT_CONTENT_NULL_RIGHTWIDTH;
1277  }
1278
1279  decideContentItemDirection(): FlexDirection {
1280    if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1 &&
1281      this.contentItem?.iconStyle && this.contentItem?.iconStyle > IconType.HEAD_SCULPTURE) {
1282      return FlexDirection.Column;
1283    }
1284    return FlexDirection.Row;
1285  }
1286
1287  decideContainerDirection(): FlexDirection {
1288    if (this.fontSizeScale < FontSizeScaleLevel.LEVEL1 || !this.contentItem) {
1289      return FlexDirection.Row;
1290    }
1291    if (this.operateItem?.button) {
1292      return FlexDirection.Column;
1293    } else if (this.operateItem?.symbolStyle) {
1294      return FlexDirection.Row;
1295    } else if (this.operateItem?.image) {
1296      return FlexDirection.Row;
1297    } else if (this.operateItem?.icon && this.operateItem?.text) {
1298      return FlexDirection.Column;
1299    } else if (this.operateItem?.arrow) {
1300      if (!this.operateItem?.text) {
1301        return FlexDirection.Row;
1302      }
1303      this.textArrowLeftSafeOffset = TEXT_SAFE_MARGIN;
1304      return FlexDirection.Column;
1305    } else if (this.operateItem?.text) {
1306      return FlexDirection.Column;
1307    } else {
1308      return FlexDirection.Row;
1309    }
1310  }
1311
1312  onFontSizeScaleChange(): void {
1313    this.containerDirection = this.decideContainerDirection();
1314    this.contentItemDirection = this.decideContentItemDirection();
1315    if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL3) {
1316      this.containerPadding = {
1317        top: $r('sys.float.padding_level12'),
1318        bottom: $r('sys.float.padding_level12'),
1319      };
1320    } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL2) {
1321      this.containerPadding = {
1322        top: $r('sys.float.padding_level10'),
1323        bottom: $r('sys.float.padding_level10'),
1324      };
1325    } else if (this.fontSizeScale >= FontSizeScaleLevel.LEVEL1) {
1326      this.containerPadding = {
1327        top: $r('sys.float.padding_level8'),
1328        bottom: $r('sys.float.padding_level8'),
1329      };
1330    } else {
1331      this.containerPadding = this.getPadding();
1332    }
1333  }
1334
1335  isSingleLine(): boolean {
1336    return !this.contentItem?.secondaryText && !this.contentItem?.description;
1337  }
1338
1339  getOperateOffset(): LengthMetrics {
1340    if (this.containerDirection === FlexDirection.Row) {
1341      return LengthMetrics.vp(0);
1342    }
1343    let iconSize = ICON_SIZE_MAP.get(this.contentItem?.iconStyle as number);
1344    if (this.contentItem?.icon && iconSize && iconSize <= HEADSCULPTURE_SIZE) {
1345      return LengthMetrics.vp(iconSize + NORMAL_ITEM_ROW_SPACE + LISTITEM_PADDING - this.textArrowLeftSafeOffset);
1346    }
1347    return LengthMetrics.vp(LISTITEM_PADDING - this.textArrowLeftSafeOffset);
1348  }
1349
1350  getMainSpace(): LengthMetrics {
1351    if (this.containerDirection === FlexDirection.Column) {
1352      return LengthMetrics.resource(this.isSingleLine() ? $r('sys.float.padding_level1') :
1353      $r('sys.float.padding_level8'));
1354    }
1355    return LengthMetrics.vp(0);
1356  }
1357
1358  getFlexOptions(): FlexOptions {
1359    if (this.containerDirection === FlexDirection.Column) {
1360      return {
1361        space: { main: this.getMainSpace() },
1362        justifyContent: FlexAlign.Center,
1363        alignItems: ItemAlign.Start,
1364        direction: this.containerDirection,
1365      };
1366    }
1367    return {
1368      justifyContent: FlexAlign.SpaceBetween,
1369      alignItems: ItemAlign.Center,
1370      direction: this.containerDirection,
1371    };
1372  }
1373
1374  decideFontSizeScale(): number {
1375    if (!this.isFollowingSystemFontScale) {
1376      return 1;
1377    }
1378    return Math.min(
1379      this.maxFontScale,
1380      (this.getUIContext().getHostContext() as common.UIAbilityContext)?.config.fontSizeScale ?? 1
1381    )
1382  }
1383
1384  getPadding(): Padding | undefined {
1385    if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
1386      let paddingNum = LengthMetrics.resource(ITEM_PADDING).value;
1387      let compareSize = paddingNum > LISTITEM_PADDING;
1388      let horizontalPadding = compareSize ? paddingNum - LISTITEM_PADDING : 0;
1389      return {
1390        top: this.isWrapText ? paddingNum : 0,
1391        bottom: this.isWrapText ? paddingNum : 0,
1392        left: horizontalPadding,
1393        right: horizontalPadding
1394      };
1395    } else {
1396      return undefined;
1397    }
1398  }
1399
1400  build() {
1401    Stack() {
1402      Flex(this.getFlexOptions()) {
1403        if (this.contentItem === null) {
1404          ContentItemStruct({
1405            isWrapText: this.isWrapText
1406          })
1407        }
1408        if (this.contentItem !== null) {
1409          ContentItemStruct({
1410            icon: this.contentItem?.icon,
1411            symbolStyle: this.contentItem?.symbolStyle,
1412            iconStyle: this.contentItem?.iconStyle,
1413            primaryText: this.contentItem?.primaryText,
1414            secondaryText: this.contentItem?.secondaryText,
1415            description: this.contentItem?.description,
1416            fontSizeScale: this.fontSizeScale,
1417            parentDirection: this.containerDirection,
1418            itemDirection: this.contentItemDirection,
1419            isFocus: this.isFocus,
1420            itemHeight: this.itemHeight,
1421            isWrapText: this.isWrapText
1422          });
1423        }
1424        if (this.operateItem !== null) {
1425          OperateItemStruct({
1426            icon: this.operateItem?.icon,
1427            subIcon: this.operateItem?.subIcon,
1428            button: this.operateItem?.button,
1429            switch: this.operateItem?.switch,
1430            checkBox: this.operateItem?.checkbox,
1431            radio: this.operateItem?.radio,
1432            image: this.operateItem?.image,
1433            symbolStyle: this.operateItem?.symbolStyle,
1434            text: this.operateItem?.text,
1435            arrow: this.operateItem?.arrow,
1436            parentCanFocus: this.canFocus,
1437            parentCanTouch: this.canTouch,
1438            parentIsHover: this.isHover,
1439            parentFrontColor: this.frontColor,
1440            parentIsActive: this.isActive,
1441            parentCanHover: this.canHover,
1442            rightWidth: this.calculatedRightWidth(),
1443            parentDirection: this.containerDirection,
1444            isFocus: this.isFocus,
1445            isChecked: this.isChecked,
1446          })
1447            .flexShrink(0)
1448            .onFocus(() => {
1449              this.canFocus = false;
1450            })
1451            .onBlur(() => {
1452              this.canFocus = true;
1453            }).padding({ start: this.getOperateOffset() });
1454        }
1455      }
1456      .height(this.containerDirection === FlexDirection.Column ? 'auto' : undefined)
1457      .constraintSize({
1458        minHeight: this.itemHeight
1459      })
1460      .focusable(IS_SUPPORT_SUBCOMPONENT_EVENT)
1461      .borderRadius($r('sys.float.composeListItem_radius'))
1462      .backgroundColor(this.frontColor)
1463      .onFocus(() => {
1464        this.canFocus = true;
1465      })
1466      .onBlur(() => {
1467        this.canFocus = false;
1468      })
1469      .onHover((isHover: boolean) => {
1470        if (this.isFocus && !IS_SUPPORT_SUBCOMPONENT_EVENT) {
1471          this.isHover = false;
1472          return;
1473        }
1474        this.isHover = isHover;
1475        if (this.canHover) {
1476          this.frontColor = isHover ? this.hoveringColor :
1477            (this.isActive ? this.activedColor : Color.Transparent.toString());
1478        }
1479        if (!IS_SUPPORT_SUBCOMPONENT_EVENT) {
1480          this.frontColor = isHover ? this.hoveringColor : NORMAL_BG_COLOR;
1481        }
1482      })
1483      .stateStyles({
1484        focused: {
1485          .border({
1486            radius: $r('sys.float.composeListItem_radius'),
1487            width: ITEM_BORDER_SHOWN,
1488            color: this.focusOutlineColor,
1489            style: BorderStyle.Solid
1490          })
1491        },
1492        normal: {
1493          .border({
1494            radius: $r('sys.float.composeListItem_radius'),
1495            color: $r('sys.color.composeListItem_stroke_normal_color'),
1496            width: $r('sys.float.composeListItem_stroke_normal_thickness'),
1497          })
1498        },
1499        pressed: {
1500          .backgroundColor(this.touchDownColor)
1501        }
1502      })
1503      .padding(this.containerPadding)
1504    }
1505    .width('100%')
1506    .accessibilityGroup(true)
1507    .accessibilityText(this.accessibilityTextBuilder)
1508    .onFocus(() => {
1509      this.isFocus = true;
1510      this.frontColor = FOCUSED_BG_COLOR;
1511    })
1512    .onBlur(() => {
1513      this.isFocus = false;
1514      this.frontColor = NORMAL_BG_COLOR;
1515    })
1516    .borderRadius(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : $r('sys.float.composeListItem_radius'))
1517    .onClick(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : () => {
1518      this.isChecked = this.operateItem?.radio ? true : !this.isChecked;
1519      if (this.operateItem?.icon && this.operateItem.icon?.action) {
1520        this.operateItem.icon.action();
1521      }
1522      if (this.operateItem?.subIcon && this.operateItem.subIcon?.action) {
1523        this.operateItem.subIcon.action();
1524      }
1525      if (this.operateItem?.arrow && this.operateItem.arrow?.action) {
1526        this.operateItem.arrow.action();
1527      }
1528    })
1529    .scale({
1530      x: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : (this.isFocus ? FOCUSED_ITEM_SCALE : RECOVER_ITEM_SCALE),
1531      y: IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : (this.isFocus ? FOCUSED_ITEM_SCALE : RECOVER_ITEM_SCALE)
1532    })
1533    .shadow(IS_SUPPORT_SUBCOMPONENT_EVENT ? undefined : (this.isFocus ? FOCUSED_SHADOW : NORMAL_SHADOW))
1534    .margin({
1535      left: !IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : undefined,
1536      right: !IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : undefined
1537    })
1538    .padding({
1539      left: IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : 0,
1540      right: IS_SUPPORT_SUBCOMPONENT_EVENT ? STACK_PADDING : 0
1541    })
1542  }
1543}