/** * Body Zone Detail Screen * Segmented level selector (Beginner default) + program list filtered by zone+level. */ import { useEffect, useMemo, useState } from 'react' import { View, Text, StyleSheet, ScrollView, Pressable, ActivityIndicator } from 'react-native' import { Stack, useRouter, useLocalSearchParams } from 'expo-router' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useTranslation } from 'react-i18next' import { Icon } from '@/src/shared/components/Icon' import { fetchProgramsByBodyZone } from '@/src/shared/data/workoutPrograms' import { BODY_ZONE_META, LEVEL_META, type BodyZone, type ProgramLevel, type WorkoutProgram, } from '@/src/shared/types/workoutProgram' import { useProgressStore } from '@/src/shared/stores/progressStore' import { useUserStore } from '@/src/shared/stores/userStore' import { TYPOGRAPHY } from '@/src/shared/constants/typography' import { SPACING } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' import { TEXT, NAVY, GREEN, BORDER_COLORS } from '@/src/shared/constants/colors' import { withOpacity } from '@/src/shared/utils/color' const LEVELS: ProgramLevel[] = ['Beginner', 'Intermediate', 'Advanced'] const VALID_ZONES: BodyZone[] = ['upper-body', 'lower-body', 'full-body'] export default function BodyZoneScreen() { const { bodyZone } = useLocalSearchParams<{ bodyZone: string }>() const router = useRouter() const insets = useSafeAreaInsets() const { t } = useTranslation() const zone = (VALID_ZONES.includes(bodyZone as BodyZone) ? bodyZone : 'full-body') as BodyZone const meta = BODY_ZONE_META[zone] const [programs, setPrograms] = useState([]) const [loading, setLoading] = useState(true) const [selectedLevel, setSelectedLevel] = useState('Beginner') const isProgramCompleted = useProgressStore(s => s.isProgramCompleted) const isPremium = useUserStore(s => s.profile.subscription) !== 'free' useEffect(() => { let cancelled = false setLoading(true) fetchProgramsByBodyZone(zone) .then(list => { if (!cancelled) setPrograms(list) }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [zone]) const filtered = useMemo( () => programs.filter(p => p.level === selectedLevel).sort((a, b) => a.sortOrder - b.sortOrder), [programs, selectedLevel], ) return ( {/* Zone header */} {meta.label} {t('screens:zone.chooseLevel')} {/* Level segmented */} {LEVELS.map(level => { const active = selectedLevel === level const levelMeta = LEVEL_META[level] return ( setSelectedLevel(level)} style={[ styles.segment, active && { backgroundColor: withOpacity(levelMeta.color, 0.2), borderColor: levelMeta.color, }, ]} > {levelMeta.label} ) })} {/* Program list */} {loading ? ( ) : filtered.length === 0 ? ( {t('screens:zone.emptyPrograms')} ) : ( {filtered.map(program => ( router.push(`/program/${program.id}`)} /> ))} )} ) } function ProgramCard({ program, completed, locked, onPress, }: { program: WorkoutProgram completed: boolean locked: boolean onPress: () => void }) { const accent = program.accentColor ?? BODY_ZONE_META[program.bodyZone].color return ( [styles.programCard, pressed && { opacity: 0.85 }]} > {program.title} {program.estimatedDuration} min · {program.tabatas.length} tabatas · {program.estimatedCalories} cal {completed && } {locked && } {!completed && !locked && } ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: NAVY[900] }, scroll: { flex: 1 }, zoneHeader: { flexDirection: 'row', alignItems: 'center', gap: SPACING[3], padding: SPACING[4], borderRadius: RADIUS.LG, marginBottom: SPACING[5], }, iconCircle: { width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center', }, zoneTitle: { ...TYPOGRAPHY.TITLE_2, color: TEXT.PRIMARY }, zoneSubtitle: { ...TYPOGRAPHY.SUBHEADLINE, color: TEXT.SECONDARY, marginTop: 2 }, segmented: { flexDirection: 'row', backgroundColor: NAVY[800], borderRadius: RADIUS.MD, padding: 4, gap: 4, marginBottom: SPACING[5], borderWidth: 1, borderColor: BORDER_COLORS.DIM, }, segment: { flex: 1, paddingVertical: SPACING[2], borderRadius: RADIUS.SM, alignItems: 'center', borderWidth: 1, borderColor: 'transparent', }, segmentText: { ...TYPOGRAPHY.SUBHEADLINE, color: TEXT.SECONDARY }, programList: { gap: SPACING[3] }, programCard: { flexDirection: 'row', alignItems: 'center', gap: SPACING[3], padding: SPACING[4], backgroundColor: NAVY[800], borderRadius: RADIUS.MD, borderWidth: 1, borderColor: BORDER_COLORS.DIM, }, programDot: { width: 10, height: 10, borderRadius: 5 }, programTitle: { ...TYPOGRAPHY.HEADLINE, color: TEXT.PRIMARY }, programMeta: { ...TYPOGRAPHY.CAPTION_1, color: TEXT.TERTIARY, marginTop: 2 }, empty: { ...TYPOGRAPHY.BODY, color: TEXT.SECONDARY, textAlign: 'center', marginTop: SPACING[8], }, })