feat: 添加聊天会话页面,支持会话ID和标题显示,优化会话列表和搜索功能
This commit is contained in:
@@ -19,7 +19,7 @@ export function ConversationHeader({
|
||||
onPress={onPressCreate}
|
||||
style={({ pressed }) => [styles.addButton, pressed && styles.addButtonPressed]}
|
||||
>
|
||||
<MaterialIcons color="#666666" name="add-circle-outline" size={36} />
|
||||
<MaterialIcons color="#666666" name="add-circle-outline" size={30} />
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { RefreshControl, StyleSheet, Text, View } from "react-native";
|
||||
|
||||
import { ConversationListItem } from "@/features/im/components/conversation-list-item";
|
||||
import type { ConversationItem } from "@/features/im/types/conversation";
|
||||
@@ -7,40 +7,45 @@ import type { ConversationItem } from "@/features/im/types/conversation";
|
||||
type ConversationListProps = {
|
||||
data: ConversationItem[];
|
||||
isLoading?: boolean;
|
||||
isRefreshing?: boolean;
|
||||
onRefresh?: () => void;
|
||||
onPressItem?: (item: ConversationItem) => void;
|
||||
};
|
||||
|
||||
export function ConversationList({
|
||||
data,
|
||||
isLoading = false,
|
||||
isRefreshing = false,
|
||||
onRefresh,
|
||||
onPressItem,
|
||||
}: ConversationListProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyTitle}>会话加载中...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyTitle}>暂无会话</Text>
|
||||
<Text style={styles.emptyHint}>试试更换关键字</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlashList
|
||||
alwaysBounceVertical
|
||||
bounces
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.id}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyTitle}>{isLoading ? "会话加载中..." : "暂无会话"}</Text>
|
||||
{!isLoading && <Text style={styles.emptyHint}>试试更换关键字</Text>}
|
||||
</View>
|
||||
}
|
||||
onRefresh={onRefresh}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
colors={["#00D48C"]}
|
||||
onRefresh={onRefresh}
|
||||
refreshing={isRefreshing}
|
||||
tintColor="#00D48C"
|
||||
/>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<ConversationListItem item={item} onPress={onPressItem} />
|
||||
)}
|
||||
refreshing={isRefreshing}
|
||||
showsVerticalScrollIndicator={false}
|
||||
/>
|
||||
);
|
||||
@@ -48,6 +53,7 @@ export function ConversationList({
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flexGrow: 1,
|
||||
paddingBottom: 28,
|
||||
paddingTop: 16,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||
import { StyleSheet, TextInput, View } from "react-native";
|
||||
import { useRef, useState } from "react";
|
||||
import { Animated, Easing, StyleSheet, TextInput } from "react-native";
|
||||
|
||||
type ConversationSearchBarProps = {
|
||||
value: string;
|
||||
@@ -10,17 +11,52 @@ export function ConversationSearchBar({
|
||||
value,
|
||||
onChangeText,
|
||||
}: ConversationSearchBarProps) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const focusAnim = useRef(new Animated.Value(0)).current;
|
||||
|
||||
const animateFocus = (toValue: 0 | 1) => {
|
||||
Animated.timing(focusAnim, {
|
||||
toValue,
|
||||
duration: 140,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.wrapper}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.wrapper,
|
||||
isFocused && styles.wrapperFocused,
|
||||
{
|
||||
transform: [
|
||||
{
|
||||
scale: focusAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 1.01],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
>
|
||||
<MaterialIcons color="#A2A2A7" name="search" size={28} />
|
||||
<TextInput
|
||||
onBlur={() => {
|
||||
setIsFocused(false);
|
||||
animateFocus(0);
|
||||
}}
|
||||
onChangeText={onChangeText}
|
||||
onFocus={() => {
|
||||
setIsFocused(true);
|
||||
animateFocus(1);
|
||||
}}
|
||||
placeholder="搜索联系人备注/昵称/ID"
|
||||
placeholderTextColor="#B8B8BC"
|
||||
style={styles.input}
|
||||
value={value}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +64,9 @@ const styles = StyleSheet.create({
|
||||
wrapper: {
|
||||
alignItems: "center",
|
||||
backgroundColor: "#F0F0F2",
|
||||
borderColor: "transparent",
|
||||
borderCurve: "continuous",
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
@@ -36,6 +74,10 @@ const styles = StyleSheet.create({
|
||||
marginHorizontal: 20,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
wrapperFocused: {
|
||||
backgroundColor: "#F4FAF8",
|
||||
borderColor: "#BEEEDB",
|
||||
},
|
||||
input: {
|
||||
color: "#2A2A2A",
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user