/** * TabataFit Home Screen - 3 Program Design * Premium Apple Fitness+ inspired layout */ import { View, StyleSheet, ScrollView, Pressable, Animated } from 'react-native' import { useRouter } from 'expo-router' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LinearGradient } from 'expo-linear-gradient' import { BlurView } from 'expo-blur' import { Icon, type IconName } from '@/src/shared/components/Icon' import { useMemo, useRef, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useHaptics } from '@/src/shared/hooks' import { useUserStore, useProgramStore, useActivityStore, getWeeklyActivity } from '@/src/shared/stores' import { PROGRAMS, ASSESSMENT_WORKOUT } from '@/src/shared/data/programs' import { StyledText } from '@/src/shared/components/StyledText' import { useThemeColors, BRAND } from '@/src/shared/theme' import type { ThemeColors } from '@/src/shared/theme/types' import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' import type { ProgramId } from '@/src/shared/types' // Feature flags — disable incomplete features const FEATURE_FLAGS = { ASSESSMENT_ENABLED: false, // Assessment player not yet implemented } const FONTS = { LARGE_TITLE: 34, TITLE: 28, TITLE_2: 22, HEADLINE: 17, BODY: 16, CAPTION: 13, } // Program metadata for display const PROGRAM_META: Record = { 'upper-body': { icon: 'dumbbell', gradient: ['#FF6B35', '#FF3B30'], accent: '#FF6B35', }, 'lower-body': { icon: 'figure.walk', gradient: ['#30D158', '#28A745'], accent: '#30D158', }, 'full-body': { icon: 'flame', gradient: ['#5AC8FA', '#007AFF'], accent: '#5AC8FA', }, } const AnimatedPressable = Animated.createAnimatedComponent(Pressable) // ═══════════════════════════════════════════════════════════════════════════ // PROGRAM CARD // ═══════════════════════════════════════════════════════════════════════════ function ProgramCard({ programId, onPress, }: { programId: ProgramId onPress: () => void }) { const { t } = useTranslation('screens') const haptics = useHaptics() const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const program = PROGRAMS[programId] const meta = PROGRAM_META[programId] const programStatus = useProgramStore((s) => s.getProgramStatus(programId)) const completion = useProgramStore((s) => s.getProgramCompletion(programId)) // Press animation const scaleValue = useRef(new Animated.Value(1)).current const handlePressIn = useCallback(() => { Animated.spring(scaleValue, { toValue: 0.97, useNativeDriver: true, speed: 50, bounciness: 4, }).start() }, [scaleValue]) const handlePressOut = useCallback(() => { Animated.spring(scaleValue, { toValue: 1, useNativeDriver: true, speed: 30, bounciness: 6, }).start() }, [scaleValue]) const statusText = { 'not-started': t('programs.status.notStarted'), 'in-progress': `${completion}% ${t('programs.status.complete')}`, 'completed': t('programs.status.completed'), }[programStatus] const handlePress = () => { haptics.buttonTap() onPress() } return ( {/* Glass Background */} {/* Color Gradient Overlay */} {/* Top Accent Line */} {/* Icon + Title Row */} {/* Gradient Icon Circle */} {t(`content:programs.${program.id}.title`)} {programStatus !== 'not-started' && ( {statusText} )} {t(`content:programs.${program.id}.description`)} {/* Progress Bar (if started) */} {programStatus !== 'not-started' && ( {programStatus === 'completed' ? t('programs.allWorkoutsComplete') : `${completion}% ${t('programs.complete')}` } )} {/* Stats — inline text, not chips */} {program.durationWeeks} {t('programs.weeks')} · {program.workoutsPerWeek}×{t('programs.perWeek')} · {program.totalWorkouts} {t('programs.workouts')} {/* Premium CTA Button — only interactive element */} {programStatus === 'not-started' ? t('programs.startProgram') : programStatus === 'completed' ? t('programs.restart') : t('programs.continue') } ) } // ═══════════════════════════════════════════════════════════════════════════ // QUICK STATS ROW // ═══════════════════════════════════════════════════════════════════════════ function QuickStats() { const { t } = useTranslation('screens') const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const streak = useActivityStore((s) => s.streak) const history = useActivityStore((s) => s.history) const weeklyActivity = useMemo(() => getWeeklyActivity(history), [history]) const thisWeekCount = weeklyActivity.filter((d) => d.completed).length const totalMinutes = useMemo(() => history.reduce((sum, r) => sum + r.durationMinutes, 0), [history]) const stats = [ { icon: 'flame.fill' as const, value: streak.current, label: t('home.statsStreak'), color: BRAND.PRIMARY }, { icon: 'calendar' as const, value: `${thisWeekCount}/7`, label: t('home.statsThisWeek'), color: '#5AC8FA' }, { icon: 'clock' as const, value: totalMinutes, label: t('home.statsMinutes'), color: '#30D158' }, ] return ( {stats.map((stat) => ( {String(stat.value)} {stat.label} ))} ) } // ═══════════════════════════════════════════════════════════════════════════ // ASSESSMENT CARD // ═══════════════════════════════════════════════════════════════════════════ function AssessmentCard({ onPress }: { onPress: () => void }) { const { t } = useTranslation('screens') const haptics = useHaptics() const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const isCompleted = useProgramStore((s) => s.assessment.isCompleted) if (isCompleted) return null const handlePress = () => { haptics.buttonTap() onPress() } return ( {/* Glass Background */} {/* Subtle brand gradient overlay */} {/* Gradient Icon Circle */} {t('assessment.title')} {ASSESSMENT_WORKOUT.duration} {t('assessment.duration')} · {ASSESSMENT_WORKOUT.exercises.length} {t('assessment.movements')} ) } // ═══════════════════════════════════════════════════════════════════════════ // MAIN SCREEN // ═══════════════════════════════════════════════════════════════════════════ export default function HomeScreen() { const { t } = useTranslation('screens') const insets = useSafeAreaInsets() const router = useRouter() const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const haptics = useHaptics() const userName = useUserStore((s) => s.profile.name) const selectedProgram = useProgramStore((s) => s.selectedProgramId) const changeProgram = useProgramStore((s) => s.changeProgram) const streak = useActivityStore((s) => s.streak) const greeting = (() => { const hour = new Date().getHours() if (hour < 12) return t('common:greetings.morning') if (hour < 18) return t('common:greetings.afternoon') return t('common:greetings.evening') })() const handleProgramPress = (programId: ProgramId) => { // Navigate to program detail router.push(`/program/${programId}` as any) } const handleAssessmentPress = () => { router.push('/assessment' as any) } const handleSwitchProgram = () => { haptics.buttonTap() changeProgram(null as any) } const programOrder: ProgramId[] = ['upper-body', 'lower-body', 'full-body'] return ( {/* Ambient gradient glow at top */} {/* Hero Section */} {greeting} {/* Inline streak badge */} {streak.current > 0 && ( {streak.current} )} {userName} {selectedProgram ? t('home.continueYourJourney') : t('home.chooseYourPath') } {/* Quick Stats Row */} {/* Assessment Card (if not completed and feature enabled) */} {FEATURE_FLAGS.ASSESSMENT_ENABLED && ( )} {/* Program Cards */} {t('home.yourPrograms')} {t('home.programsSubtitle')} {programOrder.map((programId) => ( handleProgramPress(programId)} /> ))} {/* Switch Program Option (if has progress) */} {selectedProgram && ( {t('home.switchProgram')} )} ) } // ═══════════════════════════════════════════════════════════════════════════ // STYLES // ═══════════════════════════════════════════════════════════════════════════ function createStyles(colors: ThemeColors) { return StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg.base, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: LAYOUT.SCREEN_PADDING, }, // Ambient gradient glow ambientGlow: { position: 'absolute', top: 0, left: 0, width: 300, height: 300, borderRadius: 150, }, // Hero Section heroSection: { marginTop: SPACING[4], marginBottom: SPACING[7], }, heroGreetingRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, streakBadge: { flexDirection: 'row', alignItems: 'center', gap: SPACING[1], paddingHorizontal: SPACING[3], paddingVertical: SPACING[1], borderRadius: RADIUS.FULL, backgroundColor: `${BRAND.PRIMARY}15`, borderWidth: 1, borderColor: `${BRAND.PRIMARY}30`, borderCurve: 'continuous', }, heroName: { marginTop: SPACING[1], }, heroSubtitle: { marginTop: SPACING[2], }, // Quick Stats Row quickStatsRow: { flexDirection: 'row', gap: SPACING[3], marginBottom: SPACING[7], }, quickStatPill: { flex: 1, alignItems: 'center', paddingVertical: SPACING[4], borderRadius: RADIUS.GLASS_CARD, overflow: 'hidden', borderWidth: 1, borderColor: colors.border.glass, borderCurve: 'continuous', gap: SPACING[1], backgroundColor: colors.glass.base.backgroundColor, }, // Assessment Card assessmentCard: { borderRadius: RADIUS.GLASS_CARD, overflow: 'hidden', padding: SPACING[5], marginBottom: SPACING[8], borderWidth: 1, borderColor: colors.border.glassStrong, borderCurve: 'continuous', backgroundColor: colors.glass.base.backgroundColor, }, assessmentContent: { flexDirection: 'row', alignItems: 'center', }, assessmentIconCircle: { width: 44, height: 44, borderRadius: 22, overflow: 'hidden', borderCurve: 'continuous', marginRight: SPACING[4], }, assessmentIconInner: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', }, assessmentText: { flex: 1, }, assessmentArrow: { width: 32, height: 32, borderRadius: 16, backgroundColor: `${BRAND.PRIMARY}18`, alignItems: 'center', justifyContent: 'center', borderCurve: 'continuous', }, // Programs Section programsSection: { marginTop: SPACING[2], }, sectionHeader: { marginBottom: SPACING[6], }, sectionSubtitle: { marginTop: SPACING[1], }, // Program Card programCard: { borderRadius: RADIUS.XL, marginBottom: SPACING[6], overflow: 'hidden', borderWidth: 1, borderColor: colors.border.glassStrong, borderCurve: 'continuous', backgroundColor: colors.glass.base.backgroundColor, }, accentLine: { height: 2, width: '100%', }, programCardContent: { padding: SPACING[5], paddingRight: SPACING[6], }, programCardHeader: { flexDirection: 'row', alignItems: 'flex-start', gap: SPACING[4], marginBottom: SPACING[4], }, // Gradient icon circle programIconWrapper: { width: 48, height: 48, borderRadius: 24, overflow: 'hidden', borderCurve: 'continuous', }, programIconGradient: { ...StyleSheet.absoluteFillObject, }, programIconInner: { ...StyleSheet.absoluteFillObject, alignItems: 'center', justifyContent: 'center', }, programHeaderText: { flex: 1, paddingBottom: SPACING[1], }, programTitleRow: { flexDirection: 'row', alignItems: 'center', gap: SPACING[2], marginBottom: SPACING[1], }, statusBadge: { paddingHorizontal: SPACING[2], paddingVertical: 2, borderRadius: RADIUS.FULL, borderWidth: 1, }, programTitle: { marginBottom: SPACING[1], }, programDescription: { marginBottom: SPACING[4], lineHeight: 20, }, // Progress progressContainer: { marginBottom: SPACING[4], }, progressBar: { height: 8, borderRadius: 4, marginBottom: SPACING[2], overflow: 'hidden', backgroundColor: colors.glass.inset.backgroundColor, borderCurve: 'continuous', }, progressFillWrapper: { flex: 1, }, progressFill: { height: '100%', borderRadius: 4, borderCurve: 'continuous', }, // Stats as inline meta text programMeta: { marginBottom: SPACING[4], }, // Premium CTA Button ctaButtonWrapper: { borderRadius: RADIUS.LG, overflow: 'hidden', borderCurve: 'continuous', }, ctaButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: SPACING[4], paddingHorizontal: SPACING[5], borderRadius: RADIUS.LG, borderCurve: 'continuous', }, ctaIcon: { marginLeft: SPACING[2], }, // Switch Program — glass pill switchProgramButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', alignSelf: 'center', gap: SPACING[2], paddingVertical: SPACING[3], paddingHorizontal: SPACING[6], marginTop: SPACING[2], borderRadius: RADIUS.FULL, borderWidth: 1, borderColor: colors.border.glass, borderCurve: 'continuous', overflow: 'hidden', backgroundColor: colors.glass.base.backgroundColor, }, }) }