import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { useColorScheme } from '@/hooks/useColorScheme'; import type { CheckinExercise } from '@/store/checkinSlice'; import { getDailyCheckins, removeExercise, setCurrentDate, syncCheckin, toggleExerciseCompleted } from '@/store/checkinSlice'; import { Ionicons } from '@expo/vector-icons'; import { useFocusEffect } from '@react-navigation/native'; import { useRouter } from 'expo-router'; import React, { useEffect, useMemo } from 'react'; import { Alert, FlatList, SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; function formatDate(d: Date) { const y = d.getFullYear(); const m = `${d.getMonth() + 1}`.padStart(2, '0'); const day = `${d.getDate()}`.padStart(2, '0'); return `${y}-${m}-${day}`; } export default function CheckinHome() { const dispatch = useAppDispatch(); const router = useRouter(); const today = useMemo(() => formatDate(new Date()), []); const checkin = useAppSelector((s) => (s as any).checkin); const record = checkin?.byDate?.[today]; const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; useEffect(() => { dispatch(setCurrentDate(today)); // 进入页面立即从后端获取当天打卡列表,回填本地 dispatch(getDailyCheckins(today)).unwrap().catch((err: any) => { Alert.alert('获取打卡失败', err?.message || '请稍后重试'); }); }, [dispatch, today]); useFocusEffect( React.useCallback(() => { // 返回本页时确保与后端同步(若本地有内容则上报,后台 upsert) if (record?.items && Array.isArray(record.items)) { dispatch(syncCheckin({ date: today, items: record.items as CheckinExercise[], note: record?.note })); } return () => { }; }, [dispatch, today, record?.items]) ); return ( router.back()} withSafeTop={false} transparent /> {today} 请选择动作并记录完成情况 router.push('/checkin/select')}> 新增动作 item.key} contentContainerStyle={{ paddingHorizontal: 20, paddingBottom: 20 }} ListEmptyComponent={ 还没有选择任何动作,点击“新增动作”开始吧。 } renderItem={({ item }) => ( {item.name} {item.category} 组数 {item.sets}{item.reps ? ` · 每组 ${item.reps} 次` : ''}{item.durationSec ? ` · 每组 ${item.durationSec}s` : ''} { dispatch(toggleExerciseCompleted({ date: today, key: item.key })); const nextItems: CheckinExercise[] = (record?.items || []).map((it: CheckinExercise) => it.key === item.key ? { ...it, completed: !it.completed } : it ); dispatch(syncCheckin({ date: today, items: nextItems, note: record?.note })); }} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} > Alert.alert('确认移除', '确定要移除该动作吗?', [ { text: '取消', style: 'cancel' }, { text: '移除', style: 'destructive', onPress: () => { dispatch(removeExercise({ date: today, key: item.key })); const nextItems: CheckinExercise[] = (record?.items || []).filter((it: CheckinExercise) => it.key !== item.key); dispatch(syncCheckin({ date: today, items: nextItems, note: record?.note })); }, }, ]) } > 移除 )} /> ); } const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: '#F7F8FA' }, container: { flex: 1, backgroundColor: '#F7F8FA' }, header: { paddingHorizontal: 20, paddingTop: 12, paddingBottom: 8 }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', zIndex: 2 }, backButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', backgroundColor: '#E5E7EB' }, hero: { backgroundColor: 'rgba(187,242,70,0.18)', borderRadius: 16, padding: 14 }, title: { fontSize: 24, fontWeight: '800', color: '#111827' }, subtitle: { marginTop: 6, fontSize: 12, color: '#6B7280' }, bgOrnaments: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }, blob: { position: 'absolute', width: 260, height: 260, borderRadius: 999 }, blobPrimary: { backgroundColor: '#00000000' }, blobPurple: { backgroundColor: '#00000000' }, actionRow: { paddingHorizontal: 20, marginTop: 8 }, primaryBtn: { backgroundColor: '#111827', paddingVertical: 10, borderRadius: 10, alignItems: 'center' }, primaryBtnText: { color: '#FFFFFF', fontWeight: '800' }, emptyBox: { marginTop: 16, backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginHorizontal: 0 }, emptyText: { color: '#6B7280' }, card: { marginTop: 12, marginHorizontal: 0, backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, flexDirection: 'row', alignItems: 'center', gap: 12, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3 }, cardTitle: { fontSize: 16, fontWeight: '800', color: '#111827' }, cardMeta: { marginTop: 4, fontSize: 12, color: '#6B7280' }, removeBtn: { backgroundColor: '#F3F4F6', paddingHorizontal: 10, paddingVertical: 6, borderRadius: 8 }, removeBtnText: { color: '#111827', fontWeight: '700' }, doneIconBtn: { paddingHorizontal: 4, paddingVertical: 4, borderRadius: 16, marginRight: 8 }, });