From 13faf21b8d233acf040f7f3bbd9a7d25793d5da5 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Fri, 20 Feb 2026 13:23:50 +0100 Subject: [PATCH] feat: shared components, hooks, and audio engine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components: - StyledText: unified text component replacing 5 local copies - GlassCard: reusable glassmorphic card with blur background - VideoPlayer: expo-video wrapper with preview/background modes and gradient fallback when no video URL Hooks: - useTimer: extracted timer engine with phase transitions (PREP → WORK → REST → repeat → COMPLETE), calorie tracking, and actions (start, pause, resume, skip, stop) - useHaptics: centralized haptic feedback respecting user settings - useAudio: expo-av sound effects (countdown beep, phase ding, completion chime) respecting soundEffects setting Audio assets: 3 generated WAV files for timer events. Co-Authored-By: Claude Opus 4.6 --- assets/audio/complete.wav | Bin 0 -> 48554 bytes assets/audio/countdown.wav | Bin 0 -> 8864 bytes assets/audio/phase-start.wav | Bin 0 -> 17684 bytes src/shared/components/CLAUDE.md | 11 ++ src/shared/components/GlassCard.tsx | 105 ++++++++++++++++ src/shared/components/StyledText.tsx | 50 ++++++++ src/shared/components/VideoPlayer.tsx | 78 ++++++++++++ src/shared/hooks/index.ts | 8 ++ src/shared/hooks/useAudio.ts | 74 +++++++++++ src/shared/hooks/useHaptics.ts | 45 +++++++ src/shared/hooks/useTimer.ts | 170 ++++++++++++++++++++++++++ 11 files changed, 541 insertions(+) create mode 100644 assets/audio/complete.wav create mode 100644 assets/audio/countdown.wav create mode 100644 assets/audio/phase-start.wav create mode 100644 src/shared/components/CLAUDE.md create mode 100644 src/shared/components/GlassCard.tsx create mode 100644 src/shared/components/StyledText.tsx create mode 100644 src/shared/components/VideoPlayer.tsx create mode 100644 src/shared/hooks/index.ts create mode 100644 src/shared/hooks/useAudio.ts create mode 100644 src/shared/hooks/useHaptics.ts create mode 100644 src/shared/hooks/useTimer.ts diff --git a/assets/audio/complete.wav b/assets/audio/complete.wav new file mode 100644 index 0000000000000000000000000000000000000000..8a4dac725ad54d7d77238f164bc2721915f656b1 GIT binary patch literal 48554 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 literal 0 HcmV?d00001 diff --git a/assets/audio/countdown.wav b/assets/audio/countdown.wav new file mode 100644 index 0000000000000000000000000000000000000000..4540ad30ca17de29c1857e8d6a2985778bcd4c8c GIT binary patch literal 8864 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 RGv8+}XI^ERGl5Jh^M9~6GzkCz literal 0 HcmV?d00001 diff --git a/assets/audio/phase-start.wav b/assets/audio/phase-start.wav new file mode 100644 index 0000000000000000000000000000000000000000..9db0128645527091fd261a4ab89368eedd5fa614 GIT binary patch literal 17684 zcmeI3=~q*C9>#Arl8`_Mkgxq==I(2h7&7^}s# zPOTOYP(KLmh(=OO?k-~;U5RkUj=`>zMU1E2wXU=MH&Xaej&EMgU6 z7h)eG7ms@~yU(@GC3kLcY8=-bI~^Z7avkR#YR6tD&ei0)>pl;hL=+

PpD@>$BQ08s)4Imq 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 AnE(I) literal 0 HcmV?d00001 diff --git a/src/shared/components/CLAUDE.md b/src/shared/components/CLAUDE.md new file mode 100644 index 0000000..1003e4f --- /dev/null +++ b/src/shared/components/CLAUDE.md @@ -0,0 +1,11 @@ + +# Recent Activity + + + +### Feb 18, 2026 + +| ID | Time | T | Title | Read | +|----|------|---|-------|------| +| #4889 | 4:46 PM | 🟣 | Created GlassCard component with iOS 18.4 inspired glassmorphism | ~174 | + \ No newline at end of file diff --git a/src/shared/components/GlassCard.tsx b/src/shared/components/GlassCard.tsx new file mode 100644 index 0000000..f1c992f --- /dev/null +++ b/src/shared/components/GlassCard.tsx @@ -0,0 +1,105 @@ +/** + * GlassCard - Liquid Glass Container + * iOS 18.4 inspired glassmorphism + */ + +import { ReactNode } from 'react' +import { StyleSheet, View, ViewStyle } from 'react-native' +import { BlurView } from 'expo-blur' + +import { DARK, GLASS, SHADOW, BORDER } from '../constants/colors' +import { RADIUS } from '../constants/borderRadius' + +type GlassVariant = 'base' | 'elevated' | 'inset' | 'tinted' + +interface GlassCardProps { + children: ReactNode + variant?: GlassVariant + style?: ViewStyle + hasBlur?: boolean + blurIntensity?: number +} + +const variantStyles: Record = { + base: { + backgroundColor: GLASS.BASE.backgroundColor, + borderColor: GLASS.BASE.borderColor, + borderWidth: GLASS.BASE.borderWidth, + }, + elevated: { + backgroundColor: GLASS.ELEVATED.backgroundColor, + borderColor: GLASS.ELEVATED.borderColor, + borderWidth: GLASS.ELEVATED.borderWidth, + }, + inset: { + backgroundColor: GLASS.INSET.backgroundColor, + borderColor: GLASS.INSET.borderColor, + borderWidth: GLASS.INSET.borderWidth, + }, + tinted: { + backgroundColor: GLASS.TINTED.backgroundColor, + borderColor: GLASS.TINTED.borderColor, + borderWidth: GLASS.TINTED.borderWidth, + }, +} + +const shadowStyles: Record = { + base: SHADOW.sm, + elevated: SHADOW.md, + inset: {}, + tinted: SHADOW.sm, +} + +export function GlassCard({ + children, + variant = 'base', + style, + hasBlur = true, + blurIntensity = GLASS.BLUR_MEDIUM, +}: GlassCardProps) { + const glassStyle = variantStyles[variant] + const shadowStyle = shadowStyles[variant] + + if (hasBlur) { + return ( + + + {children} + + ) + } + + return ( + + {children} + + ) +} + +const styles = StyleSheet.create({ + container: { + borderRadius: RADIUS.GLASS_CARD, + overflow: 'hidden', + }, + content: { + flex: 1, + }, +}) + +// Preset components for common use cases + +export function GlassCardElevated(props: Omit) { + return +} + +export function GlassCardInset(props: Omit) { + return +} + +export function GlassCardTinted(props: Omit) { + return +} diff --git a/src/shared/components/StyledText.tsx b/src/shared/components/StyledText.tsx new file mode 100644 index 0000000..c761203 --- /dev/null +++ b/src/shared/components/StyledText.tsx @@ -0,0 +1,50 @@ +/** + * TabataFit StyledText + * Unified text component — replaces 5 local copies + */ + +import { Text as RNText, TextStyle, StyleProp } from 'react-native' +import { TEXT } from '../constants/colors' + +type FontWeight = 'regular' | 'medium' | 'semibold' | 'bold' + +const WEIGHT_MAP: Record = { + regular: '400', + medium: '500', + semibold: '600', + bold: '700', +} + +interface StyledTextProps { + children: React.ReactNode + size?: number + weight?: FontWeight + color?: string + style?: StyleProp + numberOfLines?: number +} + +export function StyledText({ + children, + size = 17, + weight = 'regular', + color = TEXT.PRIMARY, + style, + numberOfLines, +}: StyledTextProps) { + return ( + + {children} + + ) +} diff --git a/src/shared/components/VideoPlayer.tsx b/src/shared/components/VideoPlayer.tsx new file mode 100644 index 0000000..214b09f --- /dev/null +++ b/src/shared/components/VideoPlayer.tsx @@ -0,0 +1,78 @@ +/** + * TabataFit VideoPlayer Component + * Looping muted preview mode (detail) + full-screen background mode (player) + * Falls back to gradient when no video URL + */ + +import { useRef, useEffect, useCallback } from 'react' +import { View, StyleSheet } from 'react-native' +import { useVideoPlayer, VideoView } from 'expo-video' +import { LinearGradient } from 'expo-linear-gradient' +import { BRAND } from '../constants/colors' + +interface VideoPlayerProps { + /** HLS or MP4 video URL */ + videoUrl?: string + /** Fallback gradient colors when no video */ + gradientColors?: readonly [string, string, ...string[]] + /** Looping muted preview (detail) or full-screen background (player) */ + mode?: 'preview' | 'background' + /** Whether to play the video */ + isPlaying?: boolean + style?: object +} + +export function VideoPlayer({ + videoUrl, + gradientColors = [BRAND.PRIMARY, BRAND.PRIMARY_DARK], + mode = 'preview', + isPlaying = true, + style, +}: VideoPlayerProps) { + const player = useVideoPlayer(videoUrl ?? null, (p) => { + p.loop = true + p.muted = mode === 'preview' + p.volume = mode === 'background' ? 0.3 : 0 + }) + + useEffect(() => { + if (!player || !videoUrl) return + if (isPlaying) { + player.play() + } else { + player.pause() + } + }, [isPlaying, player, videoUrl]) + + // No video URL — show gradient fallback + if (!videoUrl) { + return ( + + + + ) + } + + return ( + + + + ) +} + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + backgroundColor: '#000', + }, +}) diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts new file mode 100644 index 0000000..a7e7e08 --- /dev/null +++ b/src/shared/hooks/index.ts @@ -0,0 +1,8 @@ +/** + * TabataFit Shared Hooks + */ + +export { useTimer } from './useTimer' +export type { TimerPhase } from './useTimer' +export { useHaptics } from './useHaptics' +export { useAudio } from './useAudio' diff --git a/src/shared/hooks/useAudio.ts b/src/shared/hooks/useAudio.ts new file mode 100644 index 0000000..e0a899e --- /dev/null +++ b/src/shared/hooks/useAudio.ts @@ -0,0 +1,74 @@ +/** + * TabataFit Audio Hook + * Sound effects for timer events using expo-av + * Respects userStore soundEffects setting + */ + +import { useRef, useEffect, useCallback } from 'react' +import { Audio } from 'expo-av' +import { useUserStore } from '../stores' + +// Audio assets +const SOUNDS = { + countdown: require('../../../assets/audio/countdown.wav'), + phaseStart: require('../../../assets/audio/phase-start.wav'), + complete: require('../../../assets/audio/complete.wav'), +} + +type SoundKey = keyof typeof SOUNDS + +export function useAudio() { + const soundEnabled = useUserStore((s) => s.settings.soundEffects) + const loadedSounds = useRef>({}) + + // Configure audio session + useEffect(() => { + Audio.setAudioModeAsync({ + playsInSilentModeIOS: true, + staysActiveInBackground: false, + shouldDuckAndroid: true, + }) + + return () => { + // Unload all sounds on cleanup + Object.values(loadedSounds.current).forEach(sound => { + sound.unloadAsync().catch(() => {}) + }) + loadedSounds.current = {} + } + }, []) + + const getSound = useCallback(async (key: SoundKey): Promise => { + if (loadedSounds.current[key]) { + return loadedSounds.current[key] + } + try { + const { sound } = await Audio.Sound.createAsync(SOUNDS[key]) + loadedSounds.current[key] = sound + return sound + } catch { + return null + } + }, []) + + const play = useCallback(async (key: SoundKey) => { + if (!soundEnabled) return + const sound = await getSound(key) + if (!sound) return + try { + await sound.setPositionAsync(0) + await sound.playAsync() + } catch { + // Sound may have been unloaded + } + }, [soundEnabled, getSound]) + + return { + /** Short beep for countdown ticks (3, 2, 1) */ + countdownBeep: useCallback(() => play('countdown'), [play]), + /** Ding for phase transitions (work → rest, rest → work) */ + phaseStart: useCallback(() => play('phaseStart'), [play]), + /** Celebration chime on workout completion */ + workoutComplete: useCallback(() => play('complete'), [play]), + } +} diff --git a/src/shared/hooks/useHaptics.ts b/src/shared/hooks/useHaptics.ts new file mode 100644 index 0000000..9a48916 --- /dev/null +++ b/src/shared/hooks/useHaptics.ts @@ -0,0 +1,45 @@ +/** + * TabataFit Haptics Hook + * Centralized haptic feedback respecting user settings + */ + +import { useCallback } from 'react' +import * as Haptics from 'expo-haptics' +import { useUserStore } from '../stores' + +export function useHaptics() { + const haptics = useUserStore((s) => s.settings.haptics) + + const phaseChange = useCallback(() => { + if (!haptics) return + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy) + }, [haptics]) + + const buttonTap = useCallback(() => { + if (!haptics) return + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium) + }, [haptics]) + + const countdownTick = useCallback(() => { + if (!haptics) return + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) + }, [haptics]) + + const workoutComplete = useCallback(() => { + if (!haptics) return + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success) + }, [haptics]) + + const selection = useCallback(() => { + if (!haptics) return + Haptics.selectionAsync() + }, [haptics]) + + return { + phaseChange, + buttonTap, + countdownTick, + workoutComplete, + selection, + } +} diff --git a/src/shared/hooks/useTimer.ts b/src/shared/hooks/useTimer.ts new file mode 100644 index 0000000..4273a6c --- /dev/null +++ b/src/shared/hooks/useTimer.ts @@ -0,0 +1,170 @@ +/** + * TabataFit Timer Hook + * Extracted from player/[id].tsx — reusable timer logic + */ + +import { useRef, useEffect, useCallback } from 'react' +import { usePlayerStore } from '../stores' +import type { Workout } from '../types' + +export type TimerPhase = 'PREP' | 'WORK' | 'REST' | 'COMPLETE' + +interface UseTimerReturn { + phase: TimerPhase + timeRemaining: number + currentRound: number + totalRounds: number + currentExercise: string + nextExercise: string | undefined + progress: number + isPaused: boolean + isRunning: boolean + isComplete: boolean + calories: number + start: () => void + pause: () => void + resume: () => void + skip: () => void + stop: () => void +} + +export function useTimer(workout: Workout | null): UseTimerReturn { + const store = usePlayerStore() + const intervalRef = useRef | null>(null) + + // Load workout into store on mount + useEffect(() => { + if (workout) { + store.loadWorkout(workout) + } + return () => { + if (intervalRef.current) clearInterval(intervalRef.current) + } + }, [workout?.id]) + + const w = workout ?? { + prepTime: 10, + workTime: 20, + restTime: 10, + rounds: 8, + exercises: [{ name: 'Ready', duration: 20 }], + } + + // Calculate phase duration + const phaseDuration = + store.phase === 'PREP' ? w.prepTime : + store.phase === 'WORK' ? w.workTime : + store.phase === 'REST' ? w.restTime : 0 + + const progress = phaseDuration > 0 ? 1 - store.timeRemaining / phaseDuration : 1 + + // Exercise index based on current round + const exerciseIndex = (store.currentRound - 1) % w.exercises.length + const currentExercise = w.exercises[exerciseIndex]?.name ?? '' + const nextExercise = w.exercises[(exerciseIndex + 1) % w.exercises.length]?.name + + // Timer tick + useEffect(() => { + if (!store.isRunning || store.isPaused || store.phase === 'COMPLETE') { + if (intervalRef.current) { + clearInterval(intervalRef.current) + intervalRef.current = null + } + return + } + + intervalRef.current = setInterval(() => { + const s = usePlayerStore.getState() + if (s.timeRemaining <= 1) { + // Phase transition + if (s.phase === 'PREP') { + store.setPhase('WORK') + store.setTimeRemaining(w.workTime) + } else if (s.phase === 'WORK') { + // Add calories for completed work phase + const caloriesPerRound = workout ? Math.round(workout.calories / workout.rounds) : 5 + store.addCalories(caloriesPerRound) + + store.setPhase('REST') + store.setTimeRemaining(w.restTime) + } else if (s.phase === 'REST') { + if (s.currentRound >= (workout?.rounds ?? 8)) { + store.setPhase('COMPLETE') + store.setTimeRemaining(0) + store.setRunning(false) + } else { + store.setPhase('WORK') + store.setTimeRemaining(w.workTime) + store.setCurrentRound(s.currentRound + 1) + } + } + } else { + store.setTimeRemaining(s.timeRemaining - 1) + } + }, 1000) + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + intervalRef.current = null + } + } + }, [store.isRunning, store.isPaused, store.phase]) + + const start = useCallback(() => { + store.setRunning(true) + store.setPaused(false) + }, []) + + const pause = useCallback(() => { + store.setPaused(true) + }, []) + + const resume = useCallback(() => { + store.setPaused(false) + }, []) + + const skip = useCallback(() => { + const s = usePlayerStore.getState() + if (s.phase === 'PREP') { + store.setPhase('WORK') + store.setTimeRemaining(w.workTime) + } else if (s.phase === 'WORK') { + store.setPhase('REST') + store.setTimeRemaining(w.restTime) + } else if (s.phase === 'REST') { + if (s.currentRound >= (workout?.rounds ?? 8)) { + store.setPhase('COMPLETE') + store.setTimeRemaining(0) + store.setRunning(false) + } else { + store.setPhase('WORK') + store.setTimeRemaining(w.workTime) + store.setCurrentRound(s.currentRound + 1) + } + } + }, []) + + const stop = useCallback(() => { + store.reset() + }, []) + + return { + phase: store.phase as TimerPhase, + timeRemaining: store.timeRemaining, + currentRound: store.currentRound, + totalRounds: workout?.rounds ?? 8, + currentExercise, + nextExercise: store.phase === 'REST' ? nextExercise : undefined, + progress, + isPaused: store.isPaused, + isRunning: store.isRunning, + isComplete: store.phase === 'COMPLETE', + calories: store.calories, + start, + pause, + resume, + skip, + stop, + } +}