feat: 添加会话列表和搜索功能,重构聊天界面组件

This commit is contained in:
2026-03-09 08:12:30 +07:00
parent 007794688b
commit 212b3fa8de
12 changed files with 640 additions and 3 deletions

View File

@@ -0,0 +1,136 @@
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { Image } from "expo-image";
import { memo } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import type { ConversationItem } from "@/features/im/types/conversation";
type ConversationListItemProps = {
item: ConversationItem;
onPress?: (item: ConversationItem) => void;
};
function ConversationListItemInner({ item, onPress }: ConversationListItemProps) {
const isSystem = item.kind === "system";
return (
<Pressable
onPress={() => onPress?.(item)}
style={({ pressed }) => [styles.container, pressed && styles.containerPressed]}
>
{isSystem ? (
<View style={styles.systemAvatar}>
<MaterialIcons color="#FFFFFF" name="volume-up" size={28} />
</View>
) : (
<Image
source={item.avatarUrl}
style={styles.avatar}
transition={120}
contentFit="cover"
/>
)}
<View style={styles.content}>
<View style={styles.rowTop}>
<Text numberOfLines={1} style={styles.title}>
{item.title}
</Text>
<Text style={styles.timeLabel}>{item.timeLabel}</Text>
</View>
<View style={styles.rowBottom}>
<Text numberOfLines={1} style={styles.preview}>
{item.preview}
</Text>
{!!item.unreadCount && (
<View style={styles.unreadBadge}>
<Text style={styles.unreadText}>{item.unreadCount}</Text>
</View>
)}
</View>
</View>
</Pressable>
);
}
export const ConversationListItem = memo(ConversationListItemInner);
const styles = StyleSheet.create({
container: {
alignItems: "center",
flexDirection: "row",
gap: 12,
minHeight: 78,
paddingHorizontal: 20,
paddingVertical: 10,
},
containerPressed: {
opacity: 0.75,
},
avatar: {
backgroundColor: "#EAEAEA",
borderCurve: "continuous",
borderRadius: 24,
height: 48,
width: 48,
},
systemAvatar: {
alignItems: "center",
backgroundColor: "#50D280",
borderCurve: "continuous",
borderRadius: 24,
height: 48,
justifyContent: "center",
width: 48,
},
content: {
flex: 1,
gap: 6,
justifyContent: "center",
},
rowTop: {
alignItems: "baseline",
flexDirection: "row",
gap: 12,
justifyContent: "space-between",
},
rowBottom: {
alignItems: "center",
flexDirection: "row",
gap: 12,
justifyContent: "space-between",
},
title: {
color: "#323232",
flex: 1,
fontSize: 18,
fontWeight: "700",
},
timeLabel: {
color: "#9B9EA4",
fontSize: 12,
fontVariant: ["tabular-nums"],
fontWeight: "400",
},
preview: {
color: "#A2A6AD",
flex: 1,
fontSize: 15,
fontWeight: "400",
},
unreadBadge: {
alignItems: "center",
backgroundColor: "#00BC7D",
borderRadius: 10,
justifyContent: "center",
minWidth: 20,
paddingHorizontal: 6,
},
unreadText: {
color: "#FFFFFF",
fontSize: 12,
fontVariant: ["tabular-nums"],
fontWeight: "600",
},
});