130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { StatusBar } from "expo-status-bar";
|
|
import { useMemo, useState } from "react";
|
|
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
import { useRouter } from "expo-router";
|
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
|
import { ConversationHeader } from "@/features/im/components/conversation-header";
|
|
import { ConversationList } from "@/features/im/components/conversation-list";
|
|
import { ConversationSearchBar } from "@/features/im/components/conversation-search-bar";
|
|
import { useDebouncedValue } from "@/features/im/hooks/use-debounced-value";
|
|
import { useConversationList } from "@/features/im/hooks/use-conversation-list";
|
|
import { useConversationSearch } from "@/features/im/hooks/use-conversation-search";
|
|
import { mockConversations } from "@/features/im/mocks/mock-conversations";
|
|
import type { ConversationItem } from "@/features/im/types/conversation";
|
|
|
|
const SYSTEM_MESSAGE_ITEM: ConversationItem = {
|
|
id: "system-message",
|
|
kind: "system",
|
|
title: "系统消息",
|
|
preview: "[系统消息]",
|
|
timeLabel: "",
|
|
};
|
|
|
|
export default function IndexRoute() {
|
|
const router = useRouter();
|
|
const insets = useSafeAreaInsets();
|
|
const [keyword, setKeyword] = useState("");
|
|
const debouncedKeyword = useDebouncedValue(keyword, 180);
|
|
const { items, isLoading, isRefreshing, isLoggedIn, error, reload } = useConversationList();
|
|
|
|
const sourceItems = isLoggedIn ? items : mockConversations;
|
|
const conversationItems = useMemo(
|
|
() => [SYSTEM_MESSAGE_ITEM, ...sourceItems.filter((item) => item.kind !== "system")],
|
|
[sourceItems]
|
|
);
|
|
const filteredConversations = useConversationSearch(conversationItems, debouncedKeyword);
|
|
const unreadTotal = useMemo(() => {
|
|
const total = sourceItems.reduce((sum, item) => sum + (item.unreadCount ?? 0), 0);
|
|
if (total <= 0) {
|
|
return "0";
|
|
}
|
|
return total > 99 ? "99+" : `${total}`;
|
|
}, [sourceItems]);
|
|
|
|
return (
|
|
<View style={styles.screen}>
|
|
<StatusBar style="dark" />
|
|
|
|
<View style={[styles.headerBlock, { paddingTop: insets.top + 8 }]}>
|
|
<ConversationHeader totalLabel={unreadTotal} />
|
|
<ConversationSearchBar value={keyword} onChangeText={setKeyword} />
|
|
</View>
|
|
|
|
<View style={styles.listBlock}>
|
|
{isLoggedIn && !!error && (
|
|
<View style={styles.errorBanner}>
|
|
<Text numberOfLines={1} style={styles.errorText}>
|
|
{error}
|
|
</Text>
|
|
<Pressable onPress={reload} style={styles.errorAction}>
|
|
<Text style={styles.errorActionText}>重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
)}
|
|
|
|
<ConversationList
|
|
data={filteredConversations}
|
|
isLoading={isLoading && isLoggedIn}
|
|
isRefreshing={isRefreshing}
|
|
onRefresh={reload}
|
|
onPressItem={(item) => {
|
|
if (item.kind === "system") {
|
|
return;
|
|
}
|
|
|
|
router.push({
|
|
pathname: "/chat/[conversationId]",
|
|
params: {
|
|
conversationId: item.id,
|
|
title: item.title,
|
|
},
|
|
});
|
|
}}
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
screen: {
|
|
backgroundColor: "#F7F7F8",
|
|
flex: 1,
|
|
},
|
|
headerBlock: {
|
|
gap: 12,
|
|
paddingBottom: 14,
|
|
},
|
|
listBlock: {
|
|
backgroundColor: "#FFFFFF",
|
|
borderTopColor: "#F0F1F3",
|
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
flex: 1,
|
|
},
|
|
errorBanner: {
|
|
alignItems: "center",
|
|
backgroundColor: "#F9FFFC",
|
|
borderBottomColor: "#DCF6EB",
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
flexDirection: "row",
|
|
gap: 8,
|
|
justifyContent: "space-between",
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 10,
|
|
},
|
|
errorText: {
|
|
color: "#3D4A45",
|
|
flex: 1,
|
|
fontSize: 13,
|
|
},
|
|
errorAction: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 2,
|
|
},
|
|
errorActionText: {
|
|
color: "#00B97A",
|
|
fontSize: 13,
|
|
fontWeight: "600",
|
|
},
|
|
}); |