refactor: code quality cleanup — remove any types, add logger, rename Kine to Tabata
- Phase 0: Rename all Kine references to Tabata (types, files, imports, i18n, analytics events) - Phase 1: Add test coverage for tabataProgramStore, workoutProgramStore, and color utils (47 tests) - Phase 2: Remove all `any` types from production code with proper typed replacements - Phase 3: Replace ~60 raw console.* calls with __DEV__-gated logger utility - Phase 4: Verify .DS_Store housekeeping (already clean) 0 TypeScript errors, 583/583 tests passing.
This commit is contained in:
@@ -10,9 +10,18 @@
|
||||
| #5000 | 9:35 AM | 🔵 | Reviewed Player Screen Implementation | ~522 |
|
||||
| #4912 | 8:16 AM | 🔵 | Found doneButton component in player screen | ~104 |
|
||||
|
||||
### Feb 21, 2026
|
||||
### Apr 9, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #5551 | 12:02 AM | 🔄 | Converted onboarding and player screens to theme system | ~261 |
|
||||
| #5997 | 10:46 AM | 🟣 | Tabata Kine programs system fully implemented with four programs and specialized UI | ~563 |
|
||||
| #5996 | 10:41 AM | 🟣 | Tabata Kine programs system implementation completed | ~460 |
|
||||
| #5975 | 9:43 AM | 🟣 | Player screen updated to support kiné session detection and routing | ~316 |
|
||||
|
||||
### Apr 10, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #6005 | 10:02 AM | 🔵 | Player Screen Routing Between Kine and Legacy Workouts | ~335 |
|
||||
| #5998 | 9:52 AM | 🟣 | Tabata Kine programs system implementation completed | ~711 |
|
||||
</claude-mem-context>
|
||||
@@ -16,13 +16,15 @@ import {
|
||||
} 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 { useKeepAwake } from 'expo-keep-awake'
|
||||
import { Icon } from '@/src/shared/components/Icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useTimer } from '@/src/shared/hooks/useTimer'
|
||||
import { isTabataSession, getTabataSessionById } from '@/src/shared/data/tabata'
|
||||
import { isWorkoutProgramId, parseWorkoutProgramId, fetchProgramById, workoutProgramToTabataSession } from '@/src/shared/data/workoutPrograms'
|
||||
import { TabataPlayerScreen } from '@/src/features/player/TabataPlayerScreen'
|
||||
import type { TabataSession } from '@/src/shared/types/program'
|
||||
import { useHaptics } from '@/src/shared/hooks/useHaptics'
|
||||
import { useAudio } from '@/src/shared/hooks/useAudio'
|
||||
import { useMusicPlayer } from '@/src/shared/hooks/useMusicPlayer'
|
||||
@@ -33,10 +35,11 @@ import { useWatchSync } from '@/src/features/watch'
|
||||
|
||||
import { track } from '@/src/shared/services/analytics'
|
||||
import { VideoPlayer } from '@/src/shared/components/VideoPlayer'
|
||||
import { PHASE_COLORS, GRADIENTS, darkColors } from '@/src/shared/theme'
|
||||
import { PHASE_COLORS, darkColors } from '@/src/shared/theme'
|
||||
import { TYPOGRAPHY } from '@/src/shared/constants/typography'
|
||||
import { SPACING } from '@/src/shared/constants/spacing'
|
||||
import { RADIUS } from '@/src/shared/constants/borderRadius'
|
||||
import { GREEN, NAVY, BORDER_COLORS, TEXT } from '@/src/shared/constants/colors'
|
||||
|
||||
import {
|
||||
TimerRing,
|
||||
@@ -64,6 +67,70 @@ export default function PlayerScreen() {
|
||||
useKeepAwake()
|
||||
const router = useRouter()
|
||||
const { id } = useLocalSearchParams<{ id: string }>()
|
||||
|
||||
// ─── Dispatch: Workout Program → Tabata session → Legacy workout ─
|
||||
const sessionId = id ?? '1'
|
||||
|
||||
if (isWorkoutProgramId(sessionId)) {
|
||||
return <WorkoutProgramPlayerScreen compositeId={sessionId} />
|
||||
}
|
||||
|
||||
if (isTabataSession(sessionId)) {
|
||||
const session = getTabataSessionById(sessionId)
|
||||
if (session) {
|
||||
return <TabataPlayerScreen session={session} />
|
||||
}
|
||||
// Fallback to legacy if session not found
|
||||
}
|
||||
|
||||
return <LegacyPlayerScreen id={sessionId} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Workout Program player — async-loads a workout program from Supabase,
|
||||
* converts to TabataSession (3 tabata blocks), and renders TabataPlayerScreen.
|
||||
*/
|
||||
function WorkoutProgramPlayerScreen({ compositeId }: { compositeId: string }) {
|
||||
const [session, setSession] = React.useState<TabataSession | null | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false
|
||||
async function load() {
|
||||
const parsed = parseWorkoutProgramId(compositeId)
|
||||
if (!parsed) { if (!cancelled) setSession(null); return }
|
||||
const program = await fetchProgramById(parsed.programId)
|
||||
if (cancelled) return
|
||||
if (!program) { setSession(null); return }
|
||||
setSession(workoutProgramToTabataSession(program))
|
||||
}
|
||||
load()
|
||||
return () => { cancelled = true }
|
||||
}, [compositeId])
|
||||
|
||||
if (session === undefined) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: NAVY[900], justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ color: TEXT.SECONDARY }}>Chargement...</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: NAVY[900], justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Text style={{ color: TEXT.SECONDARY }}>Programme non trouvé</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return <TabataPlayerScreen session={session} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy player for original workout format
|
||||
*/
|
||||
function LegacyPlayerScreen({ id }: { id: string }) {
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
const haptics = useHaptics()
|
||||
const { t } = useTranslation()
|
||||
@@ -82,7 +149,7 @@ export default function PlayerScreen() {
|
||||
// Music player — synced with workout timer
|
||||
const music = useMusicPlayer({
|
||||
vibe: workout?.musicVibe ?? 'electronic',
|
||||
isPlaying: timer.isRunning && !timer.isPaused,
|
||||
isPlaying: timer.isRunning && !timer.isPaused && timer.phase !== 'PREP',
|
||||
})
|
||||
|
||||
const [showControls, setShowControls] = useState(true)
|
||||
@@ -262,11 +329,7 @@ export default function PlayerScreen() {
|
||||
{showControls && (
|
||||
<View style={[styles.header, { paddingTop: insets.top + SPACING[4] }]}>
|
||||
<Pressable onPress={stopWorkout} style={styles.closeBtn}>
|
||||
<BlurView
|
||||
intensity={colors.glass.blurLight}
|
||||
tint={colors.glass.blurTint}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<View style={StyleSheet.absoluteFill} />
|
||||
<Icon name="xmark" size={24} tintColor={colors.text.primary} />
|
||||
</Pressable>
|
||||
<View style={styles.headerCenter}>
|
||||
@@ -364,12 +427,6 @@ export default function PlayerScreen() {
|
||||
{timer.isComplete && (
|
||||
<View style={[styles.controls, { paddingBottom: insets.bottom + SPACING[6] }]}>
|
||||
<Pressable style={styles.doneButton} onPress={completeWorkout}>
|
||||
<LinearGradient
|
||||
colors={GRADIENTS.CTA}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<Text style={styles.doneButtonText}>{t('common:done')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
@@ -378,11 +435,6 @@ export default function PlayerScreen() {
|
||||
{/* Burn bar */}
|
||||
{showControls && timer.isRunning && !timer.isComplete && (
|
||||
<View style={[styles.burnBarContainer, { bottom: insets.bottom + 140 }]}>
|
||||
<BlurView
|
||||
intensity={colors.glass.blurMedium}
|
||||
tint={colors.glass.blurTint}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
<BurnBar currentCalories={timer.calories} avgCalories={workout?.calories ?? 45} />
|
||||
</View>
|
||||
)}
|
||||
@@ -404,12 +456,10 @@ export default function PlayerScreen() {
|
||||
|
||||
// ─── Styles ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const colors = darkColors
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.bg.base,
|
||||
backgroundColor: NAVY[900],
|
||||
},
|
||||
phaseBg: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
@@ -429,23 +479,24 @@ const styles = StyleSheet.create({
|
||||
closeBtn: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
borderRadius: RADIUS.FULL,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
backgroundColor: NAVY[800],
|
||||
},
|
||||
headerCenter: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
...TYPOGRAPHY.HEADLINE,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
subtitle: {
|
||||
...TYPOGRAPHY.CAPTION_1,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
},
|
||||
|
||||
// Stats overlay
|
||||
@@ -466,7 +517,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
timerTime: {
|
||||
...TYPOGRAPHY.TIMER_NUMBER,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
|
||||
@@ -489,7 +540,8 @@ const styles = StyleSheet.create({
|
||||
borderCurve: 'continuous',
|
||||
overflow: 'hidden',
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border.glass,
|
||||
borderColor: BORDER_COLORS.DIM,
|
||||
backgroundColor: NAVY[800],
|
||||
padding: SPACING[3],
|
||||
},
|
||||
|
||||
@@ -507,7 +559,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
completeTitle: {
|
||||
...TYPOGRAPHY.LARGE_TITLE,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
},
|
||||
completeSubtitle: {
|
||||
...TYPOGRAPHY.TITLE_3,
|
||||
@@ -523,27 +575,27 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
completeStatValue: {
|
||||
...TYPOGRAPHY.TITLE_1,
|
||||
color: colors.text.primary,
|
||||
color: TEXT.PRIMARY,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
completeStatLabel: {
|
||||
...TYPOGRAPHY.CAPTION_1,
|
||||
color: colors.text.tertiary,
|
||||
color: TEXT.TERTIARY,
|
||||
marginTop: SPACING[1],
|
||||
},
|
||||
doneButton: {
|
||||
width: 200,
|
||||
height: 56,
|
||||
borderRadius: RADIUS.GLASS_BUTTON,
|
||||
borderRadius: RADIUS.MD,
|
||||
borderCurve: 'continuous',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
...colors.shadow.BRAND_GLOW,
|
||||
backgroundColor: GREEN[500],
|
||||
},
|
||||
doneButtonText: {
|
||||
...TYPOGRAPHY.BUTTON_MEDIUM,
|
||||
color: colors.text.primary,
|
||||
color: NAVY[900],
|
||||
letterSpacing: 1,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user