Files
tabatago/app/timer.tsx
Millian Lamiaux 31bdb1586f feat: timer engine + full-screen timer UI
Implement the core timer feature following the src/features/ architecture:

- useTimerEngine hook: drift-free Date.now() delta countdown (100ms tick),
  explicit state machine (IDLE → GET_READY → WORK → REST → COMPLETE),
  event emitter for external consumers (PHASE_CHANGED, ROUND_COMPLETED,
  COUNTDOWN_TICK, SESSION_COMPLETE), auto-pause on AppState interruption
  (phone calls, background), expo-keep-awake during session
- TimerDisplay component: full-screen animated UI with 600ms color
  transitions between phases, pulse animation on countdown, flash red
  on last 3 seconds, round progress dots, IDLE/active/COMPLETE views
- TimerControls component: stop/pause-resume/skip buttons with Ionicons
- Timer route (app/timer.tsx): fullScreenModal wiring hook → display
- Home screen: dark theme with START button navigating to /timer
- Project docs: CLAUDE.md (constitution), PRD v1.1, skill files
- Shared constants: PHASE_COLORS, TIMER_DEFAULTS, formatTime utility
- Types: TimerPhase, TimerState, TimerConfig, TimerActions, TimerEvent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:05:25 +01:00

42 lines
1.0 KiB
TypeScript

import { useRouter } from 'expo-router'
import { useTimerEngine } from '@/src/features/timer'
import { TimerDisplay } from '@/src/features/timer/components/TimerDisplay'
export default function TimerScreen() {
const router = useRouter()
const timer = useTimerEngine()
function handleStart() {
timer.start()
}
function handleStop() {
timer.stop()
router.back()
}
return (
<TimerDisplay
state={{
phase: timer.phase,
secondsLeft: timer.secondsLeft,
currentRound: timer.currentRound,
totalRounds: timer.totalRounds,
currentCycle: timer.currentCycle,
totalCycles: timer.totalCycles,
isRunning: timer.isRunning,
isPaused: timer.isPaused,
totalElapsedSeconds: timer.totalElapsedSeconds,
}}
config={timer.config}
exerciseName="Burpees"
nextExerciseName="Squats"
onStart={handleStart}
onPause={timer.pause}
onResume={timer.resume}
onStop={handleStop}
onSkip={timer.skip}
/>
)
}