1import React, {ReactNode} from 'react'; 2import { 3 StyleProp, 4 StyleSheet, 5 TextProps, 6 TextStyle, 7 TouchableHighlight, 8 TouchableOpacity, 9 View, 10 ViewStyle, 11} from 'react-native'; 12import rpx from '@/utils/rpx'; 13import useColors from '@/hooks/useColors'; 14import ThemeText from './themeText'; 15import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; 16import { 17 fontSizeConst, 18 fontWeightConst, 19 iconSizeConst, 20} from '@/constants/uiConst'; 21import FastImage from './fastImage'; 22import {ImageStyle} from 'react-native-fast-image'; 23 24interface IListItemProps { 25 // 是否有左右边距 26 withHorizonalPadding?: boolean; 27 // 左边距 28 leftPadding?: number; 29 // 右边距 30 rightPadding?: number; 31 // height: 32 style?: StyleProp<ViewStyle>; 33 // 高度类型 34 heightType?: 'big' | 'small' | 'smallest' | 'normal' | 'none'; 35 children?: ReactNode; 36 onPress?: () => void; 37 onLongPress?: () => void; 38} 39 40const defaultPadding = rpx(24); 41const defaultActionWidth = rpx(80); 42 43const Size = { 44 big: rpx(120), 45 normal: rpx(108), 46 small: rpx(96), 47 smallest: rpx(72), 48 none: undefined, 49}; 50 51function ListItem(props: IListItemProps) { 52 const { 53 withHorizonalPadding, 54 leftPadding = defaultPadding, 55 rightPadding = defaultPadding, 56 style, 57 heightType = 'normal', 58 children, 59 onPress, 60 onLongPress, 61 } = props; 62 63 const defaultStyle: StyleProp<ViewStyle> = { 64 paddingLeft: withHorizonalPadding ? leftPadding : 0, 65 paddingRight: withHorizonalPadding ? rightPadding : 0, 66 height: Size[heightType], 67 }; 68 69 const colors = useColors(); 70 71 return ( 72 <TouchableHighlight 73 style={styles.container} 74 underlayColor={colors.listActive} 75 onPress={onPress} 76 onLongPress={onLongPress}> 77 <View style={[styles.container, defaultStyle, style]}> 78 {children} 79 </View> 80 </TouchableHighlight> 81 ); 82} 83 84interface IListItemTextProps { 85 children?: number | string; 86 fontSize?: keyof typeof fontSizeConst; 87 fontWeight?: keyof typeof fontWeightConst; 88 width?: number; 89 position?: 'left' | 'right' | 'none'; 90 fixedWidth?: boolean; 91 containerStyle?: StyleProp<ViewStyle>; 92 contentStyle?: StyleProp<TextStyle>; 93 contentProps?: TextProps; 94} 95 96function ListItemText(props: IListItemTextProps) { 97 const { 98 children, 99 fontSize, 100 fontWeight, 101 position = 'left', 102 fixedWidth, 103 width, 104 containerStyle, 105 contentStyle, 106 contentProps = {}, 107 } = props; 108 109 const defaultStyle: StyleProp<ViewStyle> = { 110 marginRight: position === 'left' ? defaultPadding : 0, 111 marginLeft: position === 'right' ? defaultPadding : 0, 112 width: fixedWidth ? width ?? defaultActionWidth : undefined, 113 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 114 }; 115 116 return ( 117 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 118 <ThemeText 119 fontSize={fontSize} 120 style={contentStyle} 121 fontWeight={fontWeight} 122 {...contentProps}> 123 {children} 124 </ThemeText> 125 </View> 126 ); 127} 128 129interface IListItemIconProps { 130 icon: string; 131 iconSize?: number; 132 width?: number; 133 position?: 'left' | 'right' | 'none'; 134 fixedWidth?: boolean; 135 containerStyle?: StyleProp<ViewStyle>; 136 contentStyle?: StyleProp<TextStyle>; 137 onPress?: () => void; 138} 139 140function ListItemIcon(props: IListItemIconProps) { 141 const { 142 icon, 143 iconSize = iconSizeConst.normal, 144 position = 'left', 145 fixedWidth, 146 width, 147 containerStyle, 148 contentStyle, 149 onPress, 150 } = props; 151 152 const colors = useColors(); 153 154 const defaultStyle: StyleProp<ViewStyle> = { 155 marginRight: position === 'left' ? defaultPadding : 0, 156 marginLeft: position === 'right' ? defaultPadding : 0, 157 width: fixedWidth ? width ?? defaultActionWidth : undefined, 158 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 159 }; 160 161 const innerContent = ( 162 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 163 <Icon 164 name={icon} 165 size={iconSize} 166 style={contentStyle} 167 color={colors.text} 168 /> 169 </View> 170 ); 171 172 return onPress ? ( 173 <TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity> 174 ) : ( 175 innerContent 176 ); 177} 178 179interface IListItemImageProps { 180 uri?: string; 181 fallbackImg?: number; 182 imageSize?: number; 183 width?: number; 184 position?: 'left' | 'right'; 185 fixedWidth?: boolean; 186 containerStyle?: StyleProp<ViewStyle>; 187 contentStyle?: StyleProp<ImageStyle>; 188} 189 190function ListItemImage(props: IListItemImageProps) { 191 const { 192 uri, 193 fallbackImg, 194 position = 'left', 195 fixedWidth, 196 width, 197 containerStyle, 198 contentStyle, 199 } = props; 200 201 const defaultStyle: StyleProp<ViewStyle> = { 202 marginRight: position === 'left' ? defaultPadding : 0, 203 marginLeft: position === 'right' ? defaultPadding : 0, 204 width: fixedWidth ? width ?? defaultActionWidth : undefined, 205 flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined, 206 }; 207 208 return ( 209 <View style={[styles.actionBase, defaultStyle, containerStyle]}> 210 <FastImage 211 style={[styles.leftImage, contentStyle]} 212 uri={uri} 213 emptySrc={fallbackImg} 214 /> 215 </View> 216 ); 217} 218 219interface IContentProps { 220 title?: ReactNode; 221 children?: ReactNode; 222 description?: ReactNode; 223 containerStyle?: StyleProp<ViewStyle>; 224} 225 226function Content(props: IContentProps) { 227 const { 228 children, 229 title = children, 230 description = null, 231 containerStyle, 232 } = props; 233 234 let realTitle; 235 let realDescription; 236 237 if (typeof title === 'string' || typeof title === 'number') { 238 realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>; 239 } else { 240 realTitle = title; 241 } 242 243 if (typeof description === 'string' || typeof description === 'number') { 244 realDescription = ( 245 <ThemeText 246 numberOfLines={1} 247 fontSize="description" 248 fontColor="textSecondary" 249 style={styles.contentDesc}> 250 {description} 251 </ThemeText> 252 ); 253 } else { 254 realDescription = description; 255 } 256 257 return ( 258 <View style={[styles.itemContentContainer, containerStyle]}> 259 {realTitle} 260 {realDescription} 261 </View> 262 ); 263} 264 265export function ListItemHeader(props: {children?: ReactNode}) { 266 const {children} = props; 267 return ( 268 <ListItem 269 withHorizonalPadding 270 heightType="smallest" 271 style={styles.listItemHeader}> 272 {typeof children === 'string' ? ( 273 <ThemeText 274 fontSize="subTitle" 275 fontColor="textSecondary" 276 fontWeight="bold"> 277 {children} 278 </ThemeText> 279 ) : ( 280 children 281 )} 282 </ListItem> 283 ); 284} 285 286const styles = StyleSheet.create({ 287 /** listitem */ 288 container: { 289 width: '100%', 290 flexDirection: 'row', 291 alignItems: 'center', 292 }, 293 /** left */ 294 actionBase: { 295 height: '100%', 296 flexShrink: 0, 297 flexGrow: 0, 298 flexBasis: 0, 299 flexDirection: 'row', 300 justifyContent: 'center', 301 alignItems: 'center', 302 }, 303 304 leftImage: { 305 width: rpx(80), 306 height: rpx(80), 307 borderRadius: rpx(16), 308 }, 309 itemContentContainer: { 310 flex: 1, 311 height: '100%', 312 justifyContent: 'center', 313 }, 314 contentDesc: { 315 marginTop: rpx(16), 316 }, 317 318 listItemHeader: { 319 marginTop: rpx(20), 320 }, 321}); 322 323ListItem.Size = Size; 324ListItem.ListItemIcon = ListItemIcon; 325ListItem.ListItemImage = ListItemImage; 326ListItem.ListItemText = ListItemText; 327ListItem.Content = Content; 328 329export default ListItem; 330