/** * TabataFit Workout Complete Screen * Celebration with real data from activity store */ import { useRef, useEffect } from 'react' import { View, Text as RNText, StyleSheet, ScrollView, Pressable, Animated, Dimensions, } from 'react-native' import { useRouter, useLocalSearchParams } from 'expo-router' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { LinearGradient } from 'expo-linear-gradient' import { BlurView } from 'expo-blur' import Ionicons from '@expo/vector-icons/Ionicons' import * as Sharing from 'expo-sharing' import { useHaptics } from '@/src/shared/hooks' import { useActivityStore } from '@/src/shared/stores' import { getWorkoutById, getTrainerById, getPopularWorkouts } from '@/src/shared/data' import { BRAND, DARK, TEXT, GLASS, SHADOW, } from '@/src/shared/constants/colors' import { TYPOGRAPHY } from '@/src/shared/constants/typography' import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' import { SPRING, EASE } from '@/src/shared/constants/animations' const { width: SCREEN_WIDTH } = Dimensions.get('window') // ═══════════════════════════════════════════════════════════════════════════ // BUTTON COMPONENTS // ═══════════════════════════════════════════════════════════════════════════ function SecondaryButton({ onPress, children, icon, }: { onPress: () => void children: React.ReactNode icon?: keyof typeof Ionicons.glyphMap }) { const scaleAnim = useRef(new Animated.Value(1)).current const handlePressIn = () => { Animated.spring(scaleAnim, { toValue: 0.97, useNativeDriver: true, ...SPRING.SNAPPY, }).start() } const handlePressOut = () => { Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true, ...SPRING.SNAPPY, }).start() } return ( {icon && } {children} ) } function PrimaryButton({ onPress, children, }: { onPress: () => void children: React.ReactNode }) { const scaleAnim = useRef(new Animated.Value(1)).current const handlePressIn = () => { Animated.spring(scaleAnim, { toValue: 0.97, useNativeDriver: true, ...SPRING.SNAPPY, }).start() } const handlePressOut = () => { Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true, ...SPRING.SNAPPY, }).start() } return ( {children} ) } // ═══════════════════════════════════════════════════════════════════════════ // COMPONENTS // ═══════════════════════════════════════════════════════════════════════════ function CelebrationRings() { const ring1Anim = useRef(new Animated.Value(0)).current const ring2Anim = useRef(new Animated.Value(0)).current const ring3Anim = useRef(new Animated.Value(0)).current useEffect(() => { Animated.stagger(200, [ Animated.spring(ring1Anim, { toValue: 1, ...SPRING.BOUNCY, useNativeDriver: true, }), Animated.spring(ring2Anim, { toValue: 1, ...SPRING.BOUNCY, useNativeDriver: true, }), Animated.spring(ring3Anim, { toValue: 1, ...SPRING.BOUNCY, useNativeDriver: true, }), ]).start() }, []) return ( 🔥 💪 ) } function StatCard({ value, label, icon, delay = 0, }: { value: string | number label: string icon: keyof typeof Ionicons.glyphMap delay?: number }) { const scaleAnim = useRef(new Animated.Value(0)).current useEffect(() => { Animated.sequence([ Animated.delay(delay), Animated.spring(scaleAnim, { toValue: 1, ...SPRING.BOUNCY, useNativeDriver: true, }), ]).start() }, [delay]) return ( {value} {label} ) } function BurnBarResult({ percentile }: { percentile: number }) { const barAnim = useRef(new Animated.Value(0)).current useEffect(() => { Animated.timing(barAnim, { toValue: percentile, duration: 1000, easing: EASE.EASE_OUT, useNativeDriver: false, }).start() }, [percentile]) const barWidth = barAnim.interpolate({ inputRange: [0, 100], outputRange: ['0%', '100%'], }) return ( Burn Bar You beat {percentile}% of users! ) } // ═══════════════════════════════════════════════════════════════════════════ // MAIN SCREEN // ═══════════════════════════════════════════════════════════════════════════ export default function WorkoutCompleteScreen() { const insets = useSafeAreaInsets() const router = useRouter() const haptics = useHaptics() const { id } = useLocalSearchParams<{ id: string }>() const workout = getWorkoutById(id ?? '1') const streak = useActivityStore((s) => s.streak) const history = useActivityStore((s) => s.history) const recentWorkouts = history.slice(0, 1) // Get the most recent result for this workout const latestResult = recentWorkouts[0] const resultCalories = latestResult?.calories ?? workout?.calories ?? 45 const resultMinutes = latestResult?.durationMinutes ?? workout?.duration ?? 4 // Recommended workouts (different from current) const recommended = getPopularWorkouts(4).filter(w => w.id !== id).slice(0, 3) const handleGoHome = () => { haptics.buttonTap() router.replace('/(tabs)') } const handleShare = async () => { haptics.selection() const isAvailable = await Sharing.isAvailableAsync() if (isAvailable) { await Sharing.shareAsync('https://tabatafit.app', { dialogTitle: `I just completed ${workout?.title ?? 'a workout'}! 🔥 ${resultCalories} calories in ${resultMinutes} minutes.`, }) } } const handleWorkoutPress = (workoutId: string) => { haptics.buttonTap() router.push(`/workout/${workoutId}`) } // Simulate percentile const burnBarPercentile = Math.min(95, Math.max(40, Math.round((resultCalories / (workout?.calories ?? 45)) * 70))) return ( {/* Celebration */} 🎉 WORKOUT COMPLETE {/* Stats Grid */} {/* Burn Bar */} {/* Streak */} {streak.current} Day Streak! Keep the momentum going! {/* Share Button */} Share Your Workout {/* Recommended */} Recommended Next {recommended.map((w) => { const trainer = getTrainerById(w.trainerId) return ( handleWorkoutPress(w.id)} style={styles.recommendedCard} > {trainer?.name[0] ?? 'T'} {w.title} {w.duration} min ) })} {/* Fixed Bottom Button */} Back to Home ) } // ═══════════════════════════════════════════════════════════════════════════ // STYLES // ═══════════════════════════════════════════════════════════════════════════ const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: DARK.BASE, }, scrollView: { flex: 1, }, scrollContent: { paddingHorizontal: LAYOUT.SCREEN_PADDING, }, // Buttons secondaryButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: SPACING[3], paddingHorizontal: SPACING[4], borderRadius: RADIUS.LG, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.3)', backgroundColor: 'transparent', }, secondaryButtonText: { ...TYPOGRAPHY.BODY, color: TEXT.PRIMARY, fontWeight: '600', }, primaryButton: { alignItems: 'center', justifyContent: 'center', paddingVertical: SPACING[4], paddingHorizontal: SPACING[6], borderRadius: RADIUS.LG, overflow: 'hidden', }, primaryButtonText: { ...TYPOGRAPHY.HEADLINE, color: TEXT.PRIMARY, fontWeight: '700', }, buttonIcon: { marginRight: SPACING[2], }, // Celebration celebrationSection: { alignItems: 'center', paddingVertical: SPACING[8], }, celebrationEmoji: { fontSize: 64, marginBottom: SPACING[4], }, celebrationTitle: { ...TYPOGRAPHY.TITLE_1, color: TEXT.PRIMARY, letterSpacing: 2, }, ringsContainer: { flexDirection: 'row', marginTop: SPACING[6], gap: SPACING[4], }, ring: { width: 64, height: 64, borderRadius: 32, backgroundColor: 'rgba(255, 255, 255, 0.1)', alignItems: 'center', justifyContent: 'center', borderWidth: 2, borderColor: 'rgba(255, 255, 255, 0.2)', }, ring1: { borderColor: BRAND.PRIMARY, backgroundColor: 'rgba(255, 107, 53, 0.15)', }, ring2: { borderColor: '#30D158', backgroundColor: 'rgba(48, 209, 88, 0.15)', }, ring3: { borderColor: '#5AC8FA', backgroundColor: 'rgba(90, 200, 250, 0.15)', }, ringEmoji: { fontSize: 28, }, // Stats Grid statsGrid: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: SPACING[6], }, statCard: { width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[4]) / 3, padding: SPACING[3], borderRadius: RADIUS.LG, alignItems: 'center', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', overflow: 'hidden', }, statValue: { ...TYPOGRAPHY.TITLE_1, color: TEXT.PRIMARY, marginTop: SPACING[2], }, statLabel: { ...TYPOGRAPHY.CAPTION_2, color: TEXT.TERTIARY, marginTop: SPACING[1], }, // Burn Bar burnBarContainer: { marginBottom: SPACING[6], }, burnBarTitle: { ...TYPOGRAPHY.HEADLINE, color: TEXT.PRIMARY, }, burnBarResult: { ...TYPOGRAPHY.BODY, color: BRAND.PRIMARY, marginTop: SPACING[1], marginBottom: SPACING[3], }, burnBarTrack: { height: 8, backgroundColor: DARK.SURFACE, borderRadius: 4, overflow: 'hidden', }, burnBarFill: { height: '100%', backgroundColor: BRAND.PRIMARY, borderRadius: 4, }, // Divider divider: { height: 1, backgroundColor: 'rgba(255, 255, 255, 0.1)', marginVertical: SPACING[2], }, // Streak streakSection: { flexDirection: 'row', alignItems: 'center', paddingVertical: SPACING[4], gap: SPACING[4], }, streakBadge: { width: 64, height: 64, borderRadius: 32, backgroundColor: 'rgba(255, 107, 53, 0.15)', alignItems: 'center', justifyContent: 'center', }, streakInfo: { flex: 1, }, streakTitle: { ...TYPOGRAPHY.TITLE_2, color: TEXT.PRIMARY, }, streakSubtitle: { ...TYPOGRAPHY.BODY, color: TEXT.TERTIARY, marginTop: SPACING[1], }, // Share shareSection: { paddingVertical: SPACING[4], alignItems: 'center', }, // Recommended recommendedSection: { paddingVertical: SPACING[4], }, recommendedTitle: { ...TYPOGRAPHY.HEADLINE, color: TEXT.PRIMARY, marginBottom: SPACING[4], }, recommendedGrid: { flexDirection: 'row', gap: SPACING[3], }, recommendedCard: { flex: 1, padding: SPACING[3], borderRadius: RADIUS.LG, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.1)', overflow: 'hidden', }, recommendedThumb: { width: '100%', aspectRatio: 1, borderRadius: RADIUS.MD, alignItems: 'center', justifyContent: 'center', marginBottom: SPACING[2], overflow: 'hidden', }, recommendedInitial: { ...TYPOGRAPHY.TITLE_1, color: TEXT.PRIMARY, }, recommendedTitleText: { ...TYPOGRAPHY.CARD_TITLE, color: TEXT.PRIMARY, }, recommendedDurationText: { ...TYPOGRAPHY.CARD_METADATA, color: TEXT.TERTIARY, }, // Bottom Bar bottomBar: { position: 'absolute', bottom: 0, left: 0, right: 0, paddingHorizontal: LAYOUT.SCREEN_PADDING, paddingTop: SPACING[4], borderTopWidth: 1, borderTopColor: 'rgba(255, 255, 255, 0.1)', }, homeButtonContainer: { height: 56, justifyContent: 'center', }, })