381 lines
12 KiB
TypeScript
381 lines
12 KiB
TypeScript
import ChatSvg from "@/assets/icons/svg/chat.svg";
|
||
import UploaderProgress from "@/components/file-upload/upload-progress/uploader-progress";
|
||
import AskNavbar from "@/components/layout/ask";
|
||
import { endUploadSessionInDb, syncUploadSessionState } from "@/features/appState/appStateSlice";
|
||
import { triggerManualUpload } from "@/lib/background-uploader/automatic";
|
||
import { getUploadTasksSince, UploadTask } from "@/lib/db";
|
||
import { fetchApi } from "@/lib/server-api-util";
|
||
import { useAppDispatch, useAppSelector } from "@/store";
|
||
import { Chat } from "@/types/ask";
|
||
import { router, useFocusEffect } from "expo-router";
|
||
import React, { useCallback, useEffect, useState } from 'react';
|
||
import { FlatList, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||
|
||
const MemoList = () => {
|
||
const insets = useSafeAreaInsets();
|
||
const dispatch = useAppDispatch();
|
||
const uploadSessionStartTime = useAppSelector((state) => state.appState.uploadSessionStartTime);
|
||
|
||
// 历史消息
|
||
const [historyList, setHistoryList] = React.useState<Chat[]>([]);
|
||
|
||
// 获取历史消息
|
||
const getHistoryList = async () => {
|
||
await fetchApi<Chat[]>(`/chats`).then((res) => {
|
||
setHistoryList(res)
|
||
})
|
||
}
|
||
|
||
// 获取对话历史消息
|
||
const getChatHistory = async (id: string) => {
|
||
// 跳转到聊天页面,并携带参数
|
||
router.push({
|
||
pathname: '/ask',
|
||
params: {
|
||
sessionId: id,
|
||
}
|
||
});
|
||
|
||
}
|
||
|
||
const handleMemoPress = (item: Chat) => {
|
||
getChatHistory(item.session_id)
|
||
}
|
||
|
||
useEffect(() => {
|
||
getHistoryList()
|
||
}, [])
|
||
|
||
const [progressInfo, setProgressInfo] = useState({ total: 0, completed: 0, image: '' });
|
||
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
let isActive = true;
|
||
let interval: any = null;
|
||
|
||
const manageUploadState = async () => {
|
||
if (!isActive) return;
|
||
|
||
// 首先,同步Redux中的会话开始时间
|
||
const action = await dispatch(syncUploadSessionState());
|
||
const sessionStartTime = action.payload as number | null;
|
||
|
||
if (sessionStartTime) {
|
||
// 如果会话存在,则获取任务进度
|
||
const allTasks = await getUploadTasksSince(sessionStartTime);
|
||
const total = allTasks.length;
|
||
const completed = allTasks.filter((t: UploadTask) => t.status === 'success' || t.status === 'failed' || t.status === 'skipped').length;
|
||
const pending = allTasks.filter((t: UploadTask) => t.status === 'pending' || t.status === 'uploading');
|
||
|
||
if (isActive) {
|
||
setProgressInfo({ total, completed, image: allTasks[0]?.uri || '' });
|
||
}
|
||
|
||
// 如果任务完成,则结束会话并清除定时器
|
||
if (total > 0 && pending.length === 0) {
|
||
console.log('MemoList detects all tasks are complete. Ending session.');
|
||
if (interval) clearInterval(interval);
|
||
dispatch(endUploadSessionInDb());
|
||
}
|
||
} else {
|
||
// 如果没有会话,确保本地状态被重置
|
||
if (isActive) {
|
||
setProgressInfo({ total: 0, completed: 0, image: '' });
|
||
}
|
||
}
|
||
};
|
||
|
||
const triggerAndMonitor = async () => {
|
||
console.log('MemoList focused, triggering foreground media upload check.');
|
||
const now = new Date();
|
||
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||
|
||
// This now returns the start time string or null
|
||
const sessionStartTimeStr = await triggerManualUpload(oneDayAgo, now);
|
||
|
||
// Only start monitoring if a session was actually started.
|
||
if (sessionStartTimeStr) {
|
||
console.log(`Upload session started with time: ${sessionStartTimeStr}, beginning to monitor...`);
|
||
// Immediately check the state once, then the interval will take over.
|
||
// This ensures the progress bar appears instantly.
|
||
manageUploadState();
|
||
interval = setInterval(manageUploadState, 2000);
|
||
}
|
||
};
|
||
|
||
triggerAndMonitor();
|
||
|
||
return () => {
|
||
isActive = false;
|
||
if (interval) {
|
||
clearInterval(interval);
|
||
}
|
||
};
|
||
}, [dispatch])
|
||
);
|
||
|
||
return (
|
||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||
{/* 上传进度展示区域 */}
|
||
{process.env.NODE_ENV === 'development' && uploadSessionStartTime && (
|
||
<View className="w-full h-20">
|
||
<UploaderProgress
|
||
imageUrl={progressInfo.image}
|
||
uploadedCount={progressInfo.completed}
|
||
totalCount={progressInfo.total}
|
||
/>
|
||
</View>
|
||
)}
|
||
|
||
{/* <View className="w-full h-full">
|
||
<AutoUploadScreen />
|
||
</View> */}
|
||
|
||
<TouchableOpacity
|
||
className='mt-2 bg-red-500 items-center h-10 justify-center'
|
||
onPress={() => router.push('/debug')}
|
||
>
|
||
<Text className="text-white">
|
||
进入db调试页面
|
||
</Text>
|
||
</TouchableOpacity>
|
||
|
||
{/* 顶部标题和上传按钮 */}
|
||
<View style={styles.header}>
|
||
<Text style={styles.title}>Memo List</Text>
|
||
</View>
|
||
|
||
{/* 历史对话 */}
|
||
<FlatList
|
||
data={historyList}
|
||
keyExtractor={(item) => item.session_id}
|
||
ItemSeparatorComponent={() => (
|
||
<View style={styles.separator} />
|
||
)}
|
||
renderItem={({ item }) => (
|
||
<TouchableOpacity
|
||
style={styles.memoItem}
|
||
onPress={() => handleMemoPress(item)}
|
||
>
|
||
<View className="w-[3rem] h-[3rem] z-1">
|
||
<ChatSvg
|
||
width="100%"
|
||
height="100%"
|
||
preserveAspectRatio="xMidYMid meet"
|
||
/>
|
||
</View>
|
||
<View style={styles.memoContent}>
|
||
<Text
|
||
style={styles.memoTitle}
|
||
numberOfLines={1}
|
||
ellipsizeMode="tail"
|
||
>
|
||
{item.title || 'memo list 历史消息'}
|
||
</Text>
|
||
<Text
|
||
style={styles.memoTitle}
|
||
numberOfLines={1}
|
||
ellipsizeMode="tail"
|
||
>
|
||
{item.latest_message?.content?.text || 'memo list 历史消息'}
|
||
</Text>
|
||
</View>
|
||
</TouchableOpacity>
|
||
)}
|
||
/>
|
||
{/* 底部导航栏 */}
|
||
<AskNavbar />
|
||
</View>
|
||
);
|
||
};
|
||
|
||
const styles = StyleSheet.create({
|
||
separator: {
|
||
height: 1,
|
||
backgroundColor: '#f0f0f0',
|
||
marginLeft: 60, // 与头像对齐
|
||
},
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: 'white',
|
||
},
|
||
header: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
padding: 16,
|
||
},
|
||
title: {
|
||
fontSize: 24,
|
||
fontWeight: 'bold',
|
||
color: '#4C320C',
|
||
},
|
||
uploadButton: {
|
||
padding: 8,
|
||
},
|
||
searchContainer: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: '#FFF',
|
||
borderRadius: 20,
|
||
marginHorizontal: 16,
|
||
paddingHorizontal: 16,
|
||
height: 48,
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 4,
|
||
elevation: 2,
|
||
},
|
||
searchIcon: {
|
||
marginRight: 8,
|
||
},
|
||
memoItem: {
|
||
flexDirection: 'row',
|
||
borderRadius: 0, // 移除圆角
|
||
padding: 16,
|
||
marginBottom: 0, // 移除底部边距
|
||
alignItems: 'center',
|
||
gap: 16,
|
||
backgroundColor: 'white',
|
||
},
|
||
avatar: {
|
||
width: 60,
|
||
height: 60,
|
||
borderRadius: 30,
|
||
marginRight: 16,
|
||
},
|
||
memoContent: {
|
||
flex: 1,
|
||
marginLeft: 12,
|
||
gap: 6,
|
||
justifyContent: 'center',
|
||
minWidth: 0, // 这行很重要,确保文本容器可以收缩到比内容更小
|
||
},
|
||
memoTitle: {
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
color: '#333',
|
||
flex: 1, // 或者 flexShrink: 1
|
||
marginLeft: 12,
|
||
},
|
||
memoSubtitle: {
|
||
fontSize: 14,
|
||
color: '#666',
|
||
},
|
||
tabBar: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-around',
|
||
alignItems: 'center',
|
||
backgroundColor: '#FFF',
|
||
borderTopWidth: 1,
|
||
borderTopColor: '#EEE',
|
||
paddingVertical: 12,
|
||
},
|
||
tabBarSvg: {
|
||
color: 'red',
|
||
},
|
||
tabItem: {
|
||
flex: 1,
|
||
alignItems: 'center',
|
||
},
|
||
tabCenter: {
|
||
width: 60,
|
||
height: 60,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
centerTabIcon: {
|
||
width: 50,
|
||
height: 50,
|
||
borderRadius: 25,
|
||
backgroundColor: '#FF9500',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginTop: -30,
|
||
},
|
||
centerTabImage: {
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: 20,
|
||
},
|
||
// 在 tabBarContainer 样式中添加
|
||
tabBarContainer: {
|
||
position: 'relative',
|
||
paddingBottom: 0,
|
||
overflow: 'visible',
|
||
marginTop: 10, // 添加一些上边距
|
||
},
|
||
tabBarContent: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-around',
|
||
alignItems: 'center',
|
||
height: 60,
|
||
position: 'relative',
|
||
backgroundColor: 'rgba(255, 255, 255, 0.7)', // 半透明白色背景
|
||
borderRadius: 30, // 圆角
|
||
marginHorizontal: 16, // 左右边距
|
||
// 添加边框效果
|
||
borderWidth: 1,
|
||
borderColor: 'rgba(255, 255, 255, 0.8)',
|
||
// 添加阴影
|
||
...Platform.select({
|
||
ios: {
|
||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||
shadowOffset: { width: 0, height: 2 },
|
||
shadowOpacity: 0.8,
|
||
shadowRadius: 4,
|
||
},
|
||
android: {
|
||
elevation: 8,
|
||
},
|
||
}),
|
||
},
|
||
// 移除之前的 tabBarBackground 样式
|
||
// 修改 centerTabShadow 样式
|
||
centerTabShadow: {
|
||
position: 'absolute',
|
||
bottom: 15,
|
||
width: 60,
|
||
height: 60,
|
||
borderRadius: 30,
|
||
backgroundColor: 'white',
|
||
...Platform.select({
|
||
ios: {
|
||
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowOpacity: 0.3,
|
||
shadowRadius: 6,
|
||
},
|
||
android: {
|
||
elevation: 10,
|
||
},
|
||
}),
|
||
},
|
||
centerTabContainer: {
|
||
flex: 1,
|
||
alignItems: 'center',
|
||
position: 'relative',
|
||
height: '100%',
|
||
},
|
||
centerTabButton: {
|
||
width: '100%',
|
||
height: '100%',
|
||
borderRadius: 30,
|
||
backgroundColor: '#FF9500',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
notificationDot: {
|
||
position: 'absolute',
|
||
top: -2,
|
||
right: -4,
|
||
width: 10,
|
||
height: 10,
|
||
borderRadius: 5,
|
||
backgroundColor: '#FF3B30',
|
||
},
|
||
});
|
||
|
||
export default MemoList; |