feat: onboarding flow (6 screens) + audio engine + design system
Onboarding: - 6-screen flow: Problem → Empathy → Solution → Wow → Personalization → Paywall - useOnboarding hook with Zustand + AsyncStorage persistence - MiniTimerDemo with live 20s timer + haptics - Auto-redirect for first-time users - Mock RevenueCat for dev testing Audio: - useAudioEngine hook with expo-av - Phase sounds (count_3/2/1, beep, bell, fanfare) - Placeholder music tracks Design System: - Typography component + constants - GlassView component - Spacing, shadows, animations, borderRadius constants - Extended color palette (phase gradients, glass, surfaces) Timer: - Fix: handle 0-duration phases (immediate advance) - Enhanced TimerDisplay with phase gradients Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,73 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'expo-router'
|
||||
import * as Haptics from 'expo-haptics'
|
||||
import { useTimerEngine } from '@/src/features/timer'
|
||||
import { useAudioEngine } from '@/src/features/audio'
|
||||
import { TimerDisplay } from '@/src/features/timer/components/TimerDisplay'
|
||||
import type { TimerEvent } from '@/src/features/timer/types'
|
||||
|
||||
export default function TimerScreen() {
|
||||
const router = useRouter()
|
||||
const timer = useTimerEngine()
|
||||
const audio = useAudioEngine()
|
||||
|
||||
// Preload audio on mount
|
||||
useEffect(() => {
|
||||
audio.preloadAll()
|
||||
return () => {
|
||||
audio.unloadAll()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Subscribe to timer events → trigger audio + haptics
|
||||
useEffect(() => {
|
||||
const unsubscribe = timer.addEventListener(async (event: TimerEvent) => {
|
||||
switch (event.type) {
|
||||
case 'PHASE_CHANGED':
|
||||
await handlePhaseChange(event.to)
|
||||
break
|
||||
|
||||
case 'COUNTDOWN_TICK':
|
||||
await audio.playPhaseSound(
|
||||
event.secondsLeft === 1 ? 'count_1' : event.secondsLeft === 2 ? 'count_2' : 'count_3'
|
||||
)
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
|
||||
break
|
||||
|
||||
case 'ROUND_COMPLETED':
|
||||
await audio.playPhaseSound('bell')
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
|
||||
break
|
||||
|
||||
case 'SESSION_COMPLETE':
|
||||
await audio.playPhaseSound('fanfare')
|
||||
await audio.stopMusic(1000)
|
||||
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
}, [audio.isLoaded])
|
||||
|
||||
async function handlePhaseChange(to: string) {
|
||||
switch (to) {
|
||||
case 'GET_READY':
|
||||
await audio.startMusic('LOW')
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
|
||||
break
|
||||
case 'WORK':
|
||||
await audio.playPhaseSound('beep_long')
|
||||
await audio.switchIntensity('HIGH')
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
|
||||
break
|
||||
case 'REST':
|
||||
await audio.playPhaseSound('beep_double')
|
||||
await audio.switchIntensity('LOW')
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleStart() {
|
||||
timer.start()
|
||||
@@ -12,6 +75,7 @@ export default function TimerScreen() {
|
||||
|
||||
function handleStop() {
|
||||
timer.stop()
|
||||
audio.stopMusic(300)
|
||||
router.back()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user