import { HeaderBar } from '@/components/ui/HeaderBar'; import { Colors } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { completeDay, setCustom } from '@/store/challengeSlice'; import type { Exercise, ExerciseCustomConfig } from '@/utils/pilatesPlan'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useState } from 'react'; import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; export default function ChallengeDayScreen() { const { day } = useLocalSearchParams<{ day: string }>(); const router = useRouter(); const dispatch = useAppDispatch(); const challenge = useAppSelector((s) => (s as any).challenge); const dayNumber = Math.max(1, Math.min(30, parseInt(String(day || '1'), 10))); const dayState = challenge?.days?.[dayNumber - 1]; const [currentSetIndexByExercise, setCurrentSetIndexByExercise] = useState>({}); const [custom, setCustomLocal] = useState(dayState?.custom || []); const isLocked = dayState?.status === 'locked'; const isCompleted = dayState?.status === 'completed'; const plan = dayState?.plan; // 不再强制所有动作完成,始终允许完成 const canFinish = true; const handleNextSet = (ex: Exercise) => { const curr = currentSetIndexByExercise[ex.key] ?? 0; if (curr < ex.sets.length) { setCurrentSetIndexByExercise((prev) => ({ ...prev, [ex.key]: curr + 1 })); } }; const handleComplete = async () => { // 持久化自定义配置 await dispatch(setCustom({ dayNumber, custom: custom })); await dispatch(completeDay(dayNumber)); router.back(); }; const updateCustom = (key: string, partial: Partial) => { setCustomLocal((prev) => { const next = prev.map((c) => (c.key === key ? { ...c, ...partial } : c)); return next; }); }; if (!plan) { return ( 加载中... ); } return ( router.back()} withSafeTop={false} transparent /> {plan.title} {plan.focus} item.key} contentContainerStyle={{ paddingHorizontal: 20, paddingBottom: 120 }} renderItem={({ item }) => { const doneSets = currentSetIndexByExercise[item.key] ?? 0; const conf = custom.find((c) => c.key === item.key); const targetSets = conf?.sets ?? item.sets.length; const perSetDuration = conf?.durationSec ?? item.sets[0]?.durationSec ?? 40; return ( {item.name} {item.description} updateCustom(item.key, { enabled: !(conf?.enabled ?? true) })}> {conf?.enabled === false ? '已关闭' : '已启用'} 组数 updateCustom(item.key, { sets: Math.max(1, (conf?.sets ?? targetSets) - 1) })}>- {conf?.sets ?? targetSets} updateCustom(item.key, { sets: Math.min(10, (conf?.sets ?? targetSets) + 1) })}>+ 时长/组 updateCustom(item.key, { durationSec: Math.max(10, (conf?.durationSec ?? perSetDuration) - 5) })}>- {conf?.durationSec ?? perSetDuration}s updateCustom(item.key, { durationSec: Math.min(180, (conf?.durationSec ?? perSetDuration) + 5) })}>+ {Array.from({ length: targetSets }).map((_, idx) => ( {perSetDuration}s ))} handleNextSet(item)} disabled={doneSets >= targetSets || conf?.enabled === false}> {doneSets >= item.sets.length ? '本动作完成' : '完成一组'} {item.tips && ( {item.tips.map((t: string, i: number) => ( • {t} ))} )} ); }} /> {isCompleted ? '已完成' : '完成今日训练'} ); } const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: '#F7F8FA' }, container: { flex: 1, backgroundColor: '#F7F8FA' }, header: { paddingHorizontal: 20, paddingTop: 10, paddingBottom: 10 }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, backButton: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', backgroundColor: '#E5E7EB' }, headerTitle: { fontSize: 18, fontWeight: '800', color: '#1A1A1A' }, title: { marginTop: 6, fontSize: 20, fontWeight: '800', color: '#1A1A1A' }, subtitle: { marginTop: 6, fontSize: 12, color: '#6B7280' }, exerciseCard: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginTop: 12, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3, }, exerciseHeader: { marginBottom: 8 }, exerciseName: { fontSize: 16, fontWeight: '800', color: '#111827' }, exerciseDesc: { marginTop: 4, fontSize: 12, color: '#6B7280' }, setsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 8 }, controlsRow: { flexDirection: 'row', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginTop: 8 }, toggleBtn: { backgroundColor: '#111827', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8 }, toggleBtnOff: { backgroundColor: '#9CA3AF' }, toggleBtnText: { color: '#FFFFFF', fontWeight: '700' }, counterBox: { backgroundColor: '#F3F4F6', borderRadius: 8, padding: 8 }, counterLabel: { fontSize: 10, color: '#6B7280' }, counterRow: { flexDirection: 'row', alignItems: 'center' }, counterBtn: { backgroundColor: '#E5E7EB', width: 28, height: 28, borderRadius: 6, alignItems: 'center', justifyContent: 'center' }, counterBtnText: { fontWeight: '800', color: '#111827' }, counterValue: { minWidth: 40, textAlign: 'center', fontWeight: '700', color: '#111827' }, setPill: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 999 }, setPillTodo: { backgroundColor: '#F3F4F6' }, setPillDone: { backgroundColor: Colors.light.accentGreen }, setPillText: { fontSize: 12, fontWeight: '700' }, setPillTextTodo: { color: '#6B7280' }, setPillTextDone: { color: '#192126' }, nextSetBtn: { marginTop: 10, alignSelf: 'flex-start', backgroundColor: '#111827', paddingHorizontal: 12, paddingVertical: 8, borderRadius: 8 }, nextSetText: { color: '#FFFFFF', fontWeight: '700' }, tipsBox: { marginTop: 10, backgroundColor: '#F9FAFB', borderRadius: 8, padding: 10 }, tipText: { fontSize: 12, color: '#6B7280', lineHeight: 18 }, bottomBar: { position: 'absolute', left: 0, right: 0, bottom: 0, padding: 20, backgroundColor: 'transparent' }, finishBtn: { backgroundColor: Colors.light.accentGreen, paddingVertical: 14, borderRadius: 999, alignItems: 'center' }, finishBtnText: { color: '#192126', fontWeight: '800', fontSize: 16 }, });