memowake-front/app/(tabs)/memo-list.tsx

399 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { exist_pending_tasks, 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 (restore_session: boolean = false) => {
if (!isActive) {
console.log('MemoList manageUploadState is not active');
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 initializeUploadProcess = async () => {
// First, check if a session is already active.
const action = await dispatch(syncUploadSessionState());
const existingSessionStartTime = action.payload as number | null;
const existPendingTasks = await exist_pending_tasks();
if (existingSessionStartTime && existPendingTasks) {
console.log('MemoList focused, existing session found. Monitoring progress.');
// If a session exists, just start monitoring.
manageUploadState(true); // Initial check
interval = setInterval(manageUploadState, 2000);
} else {
// If no session, then try to trigger a new upload.
console.log('MemoList focused, no existing session. Triggering foreground media upload check.');
const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const newSessionStartTimeStr = await triggerManualUpload(oneDayAgo, now);
if (newSessionStartTimeStr) {
console.log(`New upload session started with time: ${newSessionStartTimeStr}, beginning to monitor...`);
// A new session was started, so start monitoring.
manageUploadState(); // Initial check
interval = setInterval(manageUploadState, 2000);
}
}
};
initializeUploadProcess();
return () => {
isActive = false;
if (interval) {
clearInterval(interval);
}
};
}, [dispatch])
);
const renderHeader = () => (
<>
{process.env.NODE_ENV === 'development' && <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>
{/* 上传进度展示区域 */}
{uploadSessionStartTime && progressInfo.total > 0 && (
<View className="h-10 mt-6 mb-2 mx-4">
<UploaderProgress
imageUrl={progressInfo.image}
uploadedCount={progressInfo.completed}
totalCount={progressInfo.total}
/>
</View>
)}
</>
);
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
{/* <View className="w-full h-full">
<AutoUploadScreen />
</View> */}
{/* 历史对话 */}
<FlatList
ListHeaderComponent={renderHeader}
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;