/**
* 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',
},
})