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