From fa189fe72e93162823a3fbb2fe8a41a390bb4852 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Tue, 17 Feb 2026 21:52:23 +0100 Subject: [PATCH] feat: onboarding flow (6 screens) + audio engine + design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/(tabs)/_layout.tsx | 36 +- app/(tabs)/index.tsx | 149 ++++- app/_layout.tsx | 28 + app/onboarding/index.tsx | 49 ++ app/timer.tsx | 64 ++ assets/audio/music/electro_high.mp3 | Bin 0 -> 120244 bytes assets/audio/music/electro_low.mp3 | Bin 0 -> 120244 bytes assets/audio/sounds/beep_double.mp3 | Bin 0 -> 2203 bytes assets/audio/sounds/beep_long.mp3 | Bin 0 -> 2542 bytes assets/audio/sounds/beep_short.mp3 | Bin 0 -> 1502 bytes assets/audio/sounds/bell.mp3 | Bin 0 -> 3972 bytes assets/audio/sounds/count_1.mp3 | Bin 0 -> 2152 bytes assets/audio/sounds/count_2.mp3 | Bin 0 -> 1398 bytes assets/audio/sounds/count_3.mp3 | Bin 0 -> 1398 bytes assets/audio/sounds/fanfare.mp3 | Bin 0 -> 5741 bytes package-lock.json | 108 ++- package.json | 11 +- src/features/audio/data/sounds.ts | 13 + src/features/audio/data/tracks.ts | 27 + src/features/audio/hooks/useAudioEngine.ts | 173 +++++ src/features/audio/index.ts | 8 + src/features/audio/types.ts | 31 + .../onboarding/components/ChoiceButton.tsx | 100 +++ .../onboarding/components/MiniTimerDemo.tsx | 178 +++++ .../components/OnboardingScreen.tsx | 46 ++ .../onboarding/components/PaywallCard.tsx | 158 +++++ .../onboarding/components/PrimaryButton.tsx | 85 +++ .../onboarding/components/ProgressBar.tsx | 89 +++ src/features/onboarding/components/index.ts | 6 + src/features/onboarding/data/barriers.ts | 15 + src/features/onboarding/data/goals.ts | 15 + src/features/onboarding/data/index.ts | 5 + src/features/onboarding/data/levels.ts | 14 + .../onboarding/hooks/useOnboarding.ts | 137 ++++ src/features/onboarding/index.ts | 26 + .../onboarding/screens/Screen1Problem.tsx | 165 +++++ .../onboarding/screens/Screen2Empathy.tsx | 104 +++ .../onboarding/screens/Screen3Solution.tsx | 251 +++++++ .../onboarding/screens/Screen4WowMoment.tsx | 149 +++++ .../screens/Screen5Personalization.tsx | 186 ++++++ .../onboarding/screens/Screen6Paywall.tsx | 234 +++++++ src/features/onboarding/screens/index.ts | 2 + src/features/onboarding/types.ts | 20 + .../timer/components/TimerControls.tsx | 22 +- .../timer/components/TimerDisplay.tsx | 628 ++++++++++-------- src/features/timer/hooks/useTimerEngine.ts | 3 + src/shared/components/GlassView.tsx | 32 + src/shared/components/Typography.tsx | 25 + src/shared/constants/animations.ts | 51 ++ src/shared/constants/borderRadius.ts | 11 + src/shared/constants/colors.ts | 69 ++ src/shared/constants/index.ts | 16 + src/shared/constants/shadows.ts | 44 ++ src/shared/constants/spacing.ts | 25 + src/shared/constants/typography.ts | 73 ++ 55 files changed, 3361 insertions(+), 320 deletions(-) create mode 100644 app/onboarding/index.tsx create mode 100644 assets/audio/music/electro_high.mp3 create mode 100644 assets/audio/music/electro_low.mp3 create mode 100644 assets/audio/sounds/beep_double.mp3 create mode 100644 assets/audio/sounds/beep_long.mp3 create mode 100644 assets/audio/sounds/beep_short.mp3 create mode 100644 assets/audio/sounds/bell.mp3 create mode 100644 assets/audio/sounds/count_1.mp3 create mode 100644 assets/audio/sounds/count_2.mp3 create mode 100644 assets/audio/sounds/count_3.mp3 create mode 100644 assets/audio/sounds/fanfare.mp3 create mode 100644 src/features/audio/data/sounds.ts create mode 100644 src/features/audio/data/tracks.ts create mode 100644 src/features/audio/hooks/useAudioEngine.ts create mode 100644 src/features/audio/index.ts create mode 100644 src/features/audio/types.ts create mode 100644 src/features/onboarding/components/ChoiceButton.tsx create mode 100644 src/features/onboarding/components/MiniTimerDemo.tsx create mode 100644 src/features/onboarding/components/OnboardingScreen.tsx create mode 100644 src/features/onboarding/components/PaywallCard.tsx create mode 100644 src/features/onboarding/components/PrimaryButton.tsx create mode 100644 src/features/onboarding/components/ProgressBar.tsx create mode 100644 src/features/onboarding/components/index.ts create mode 100644 src/features/onboarding/data/barriers.ts create mode 100644 src/features/onboarding/data/goals.ts create mode 100644 src/features/onboarding/data/index.ts create mode 100644 src/features/onboarding/data/levels.ts create mode 100644 src/features/onboarding/hooks/useOnboarding.ts create mode 100644 src/features/onboarding/index.ts create mode 100644 src/features/onboarding/screens/Screen1Problem.tsx create mode 100644 src/features/onboarding/screens/Screen2Empathy.tsx create mode 100644 src/features/onboarding/screens/Screen3Solution.tsx create mode 100644 src/features/onboarding/screens/Screen4WowMoment.tsx create mode 100644 src/features/onboarding/screens/Screen5Personalization.tsx create mode 100644 src/features/onboarding/screens/Screen6Paywall.tsx create mode 100644 src/features/onboarding/screens/index.ts create mode 100644 src/features/onboarding/types.ts create mode 100644 src/shared/components/GlassView.tsx create mode 100644 src/shared/components/Typography.tsx create mode 100644 src/shared/constants/animations.ts create mode 100644 src/shared/constants/borderRadius.ts create mode 100644 src/shared/constants/index.ts create mode 100644 src/shared/constants/shadows.ts create mode 100644 src/shared/constants/spacing.ts create mode 100644 src/shared/constants/typography.ts diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 54e11d0..8efaea6 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,35 +1,41 @@ -import { Tabs } from 'expo-router'; -import React from 'react'; +import { Tabs } from 'expo-router' +import Ionicons from '@expo/vector-icons/Ionicons' -import { HapticTab } from '@/components/haptic-tab'; -import { IconSymbol } from '@/components/ui/icon-symbol'; -import { Colors } from '@/constants/theme'; -import { useColorScheme } from '@/hooks/use-color-scheme'; +import { HapticTab } from '@/components/haptic-tab' +import { BRAND, SURFACE, TEXT, BORDER } from '@/src/shared/constants/colors' export default function TabLayout() { - const colorScheme = useColorScheme(); - return ( + tabBarActiveTintColor: BRAND.PRIMARY, + tabBarInactiveTintColor: TEXT.HINT, + tabBarStyle: { + backgroundColor: SURFACE.BASE, + borderTopColor: BORDER.SUBTLE, + }, + }} + > , + title: 'Accueil', + tabBarIcon: ({ color, size }) => ( + + ), }} /> , + title: 'Explorer', + tabBarIcon: ({ color, size }) => ( + + ), }} /> - ); + ) } diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 0905dc4..9ff99ad 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,67 +1,156 @@ -import { Pressable, StyleSheet, Text, View } from 'react-native' -import { useRouter } from 'expo-router' +import { useEffect, useRef } from 'react' +import { + Animated, + Pressable, + StyleSheet, + Text, + View, +} from 'react-native' +import { useRouter, Redirect } from 'expo-router' import { StatusBar } from 'expo-status-bar' +import { LinearGradient } from 'expo-linear-gradient' import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { BRAND, TEXT, APP_GRADIENTS, ACCENT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SHADOW, TEXT_SHADOW } from '@/src/shared/constants/shadows' +import { DURATION, EASING } from '@/src/shared/constants/animations' +import { useIsOnboardingComplete } from '@/src/features/onboarding/hooks/useOnboarding' + export default function HomeScreen() { const router = useRouter() const insets = useSafeAreaInsets() + const isOnboardingComplete = useIsOnboardingComplete() + + const glowAnim = useRef(new Animated.Value(0)).current + + // Show nothing while Zustand hydrates + if (isOnboardingComplete === undefined) { + return null + } + + // Redirect to onboarding if not complete + if (isOnboardingComplete === false) { + return + } + + useEffect(() => { + Animated.loop( + Animated.sequence([ + Animated.timing(glowAnim, { + toValue: 1, + duration: DURATION.BREATH, + easing: EASING.STANDARD, + useNativeDriver: false, + }), + Animated.timing(glowAnim, { + toValue: 0, + duration: DURATION.BREATH, + easing: EASING.STANDARD, + useNativeDriver: false, + }), + ]) + ).start() + }, []) + + const glowOpacity = glowAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0.15, 0.4], + }) + + const glowScale = glowAnim.interpolate({ + inputRange: [0, 1], + outputRange: [1, 1.12], + }) return ( - + - TABATAGO - Entraînement Tabata + + TABATA + GO + 4 minutes. Tout donner. + - [ - styles.startButton, - pressed && styles.startButtonPressed, - ]} - onPress={() => router.push('/timer')} - > - START - - + + + [ + styles.startButton, + pressed && styles.startButtonPressed, + ]} + onPress={() => router.push('/timer')} + > + START + + + ) } const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#1E1E2E', alignItems: 'center', justifyContent: 'center', - gap: 12, + }, + brandArea: { + alignItems: 'center', }, title: { - fontSize: 44, - fontWeight: '900', - color: '#FFFFFF', - letterSpacing: 6, + ...TYPOGRAPHY.brandTitle, + color: BRAND.PRIMARY, + ...TEXT_SHADOW.BRAND, }, subtitle: { - fontSize: 18, - color: 'rgba(255, 255, 255, 0.6)', - fontWeight: '500', + ...TYPOGRAPHY.displaySmall, + color: TEXT.PRIMARY, + marginTop: -6, + }, + tagline: { + ...TYPOGRAPHY.caption, + color: TEXT.HINT, + fontStyle: 'italic', + marginTop: 12, + }, + buttonArea: { + marginTop: 72, + alignItems: 'center', + justifyContent: 'center', + }, + buttonGlow: { + position: 'absolute', + width: 172, + height: 172, + borderRadius: 86, + backgroundColor: ACCENT.ORANGE, }, startButton: { width: 160, height: 160, borderRadius: 80, - backgroundColor: '#F97316', + backgroundColor: BRAND.PRIMARY, alignItems: 'center', justifyContent: 'center', - marginTop: 60, + ...SHADOW.BRAND_GLOW, }, startButtonPressed: { - opacity: 0.7, + transform: [{ scale: 0.95 }], }, startButtonText: { - fontSize: 32, - fontWeight: '900', - color: '#FFFFFF', + ...TYPOGRAPHY.buttonHero, + color: TEXT.PRIMARY, letterSpacing: 4, }, }) diff --git a/app/_layout.tsx b/app/_layout.tsx index 58bd698..f13a8c1 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,9 +1,15 @@ +import { useEffect } from 'react'; import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; +import * as SplashScreen from 'expo-splash-screen'; import 'react-native-reanimated'; import { useColorScheme } from '@/hooks/use-color-scheme'; +import { useIsOnboardingComplete } from '@/src/features/onboarding/hooks/useOnboarding'; + +// Prevent splash screen from auto-hiding +SplashScreen.preventAutoHideAsync(); export const unstable_settings = { anchor: '(tabs)', @@ -11,10 +17,32 @@ export const unstable_settings = { export default function RootLayout() { const colorScheme = useColorScheme(); + const isOnboardingComplete = useIsOnboardingComplete(); + + // Hide splash screen once we have a definite state + useEffect(() => { + if (isOnboardingComplete !== undefined) { + SplashScreen.hideAsync(); + } + }, [isOnboardingComplete]); + + // Show nothing while Zustand hydrates from AsyncStorage + if (isOnboardingComplete === undefined) { + return null; + } return ( + state.currentStep) + const isOnboardingComplete = useOnboarding((state) => state.isOnboardingComplete) + const nextStep = useOnboarding((state) => state.nextStep) + const completeOnboarding = useOnboarding((state) => state.completeOnboarding) + + const handleNext = () => { + nextStep() + } + + const handleComplete = () => { + completeOnboarding() + router.replace('/(tabs)') + } + + // Redirect to tabs if onboarding is already complete + if (isOnboardingComplete) { + return + } + + // Render the correct screen based on current step + switch (currentStep) { + case 0: + return + case 1: + return + case 2: + return + case 3: + return + case 4: + return + case 5: + return + default: + // Fallback to first screen if step is out of bounds + return + } +} diff --git a/app/timer.tsx b/app/timer.tsx index b2b31c8..771d2e1 100644 --- a/app/timer.tsx +++ b/app/timer.tsx @@ -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() } diff --git a/assets/audio/music/electro_high.mp3 b/assets/audio/music/electro_high.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..3650371191cfd88b76c319d2de5bcf9da7575ae9 GIT binary patch literal 120244 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 Lo#V7$0`>m^k`0+Y literal 0 HcmV?d00001 diff --git a/assets/audio/music/electro_low.mp3 b/assets/audio/music/electro_low.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5bd61b57242938a153c57e195dc07b8d787c1356 GIT binary patch literal 120244 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 literal 0 HcmV?d00001 diff --git a/assets/audio/sounds/beep_long.mp3 b/assets/audio/sounds/beep_long.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..68232bf129dfca45c7939792f3e6125310094628 GIT binary patch literal 2542 zcmcJP3rtg27{|Z%wxtCvpgcN;Qa~KDP+kUM7>3g7n1D>dQP7D`iUYHkThV0}3k*~g z7${RW%EX{p-;gnFmTN^s1jGjj3A$nslrp&+AP{ujxwlF-TB3&dPkMXKxwrq{_qq)e z`7wZ$OZ4*PV)V@gz{1yjz0u!CI8W%~?M>Y@T=b(M_RY?h_R7a_ql9o~rr9n!1QY0D0xDLhW6G(1eXcjZpu;gg6 zu6n;mt@Wd)s{z>S-6EoKaLz0&*Zbc@H`o`RR4{e*_4Ri~EjVj=)4?0VI{Sw%LWtEA zmPk9iok5$LF!HqI>QYPM^H#Z23)it4Mju?nYuH$yip{H?BK3yTt+r|MD83B~86FVV zRDCeBkLMhgolSRbT~p2rOJ~YA-EW)j6zL%SL1H2GJQi$TTg=H95S82;8NAhh1zRgG zpq2j0u{>G6k5E56=<8*_d&!YMdRXZt{G{tGpM~Mb`B}{duLX&PVR`Q~WkeE7u5B}0 z-%|D{WbrRFU56UXqh{2V^i8|(QG3cGd_jcRkL(&%RZ|zZMW~S_EL0A*tqsOf>`MI$ z;R$gMrqo6a)p>Du$gY19_i`H}Yv?5IlD-+@_6>K5t+hGM6)I&!&7o6FrOa0#h#9kt zc?BV}lcZT87Q5l(NUx{)^<)*Tp(74#e0M&b+o&dHD?Z&dXUx(l7!wE3Wr!~=I;dmc zm)-d}a|F^@mRF_}qHTX2`6;zE8xx-OeJx9h0+)$KGSs7%>x4@WEh;Vh!d|T(Zt^)WXOjpHBhyWW}d(w&2s;p4E*X1C_z2^+i#D)h8-A|Mx*nDR%r-jToSNeO~ z?Z2%qt9Jh{bp5DR^+UNv?i7OyT%@6xZrDs4W1=eKkKl@dq~f-2QH@l(H``WtVDXyV zKyJZ3us2| z;oxs?FZ$-7a&^&G6P!n&`PNy23+PfYrVF5H#p8XA_Hc)2AVC>(&i`|^*uNq6ND1HJ z-di{=fL4`yBK5~zD-K#6Fv1vWe;j-P*lP$p1Xw>+oTQT}Sb| z7ndrAc>DK2H*uBj@m|G^v3b54Mpgycxl%ZFJBrsr*PaFX+<{E{Q$c;0tk yf%0Lh-&Eat;S#vTkenz+Srs;4G`Y}C{+i6EO@ZIWT$BKOfVpx2hRG)!W literal 0 HcmV?d00001 diff --git a/assets/audio/sounds/beep_short.mp3 b/assets/audio/sounds/beep_short.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bf09b46c2a2a011d3335b92193172e631488cd62 GIT binary patch literal 1502 zcmeZtF=k-^0i}@OU{@f`$H2hslUSB!W~gVbXJ}vmmV^-he>)sN;zF1anR)3z7C#WP zGcd4jV(3KyOHjZz6mSw1+(rR!QNVu`0J0hA#$=!y4J|<&7*Ju(gfLk*`S53K2DAVF z-f#rywjau#As-kNizmFQH@gIs;Mo0b0gEla^SNT5%Yo}T92gU1+a&b^_t*WAVPRx& zIPjrHzGi_!v2gPrfs*Ge_ouU%GNpg05hz)pCV%jQgK~qbRaZj;4^tz91JE!QRtb)x z&jyZ(3C1=|hUUq;Ss2>e^jWWjL|pOe2}wB5e?WmPL20_9DQl_v!ARkUMt^jjw(vHs($lsLI#s`pQIG%Z;5yN&()2cW5KIeWZUuV$LD(5Y4G+1~T7SqoGB zzeELTgx4wPerM1!xN|jd=kg`DHgPPeKP{~B&nmKSTIL2lA4gwTV^GZf|G~ER0nnJt z>>z?A2-yhzk~LY$$&hJc(ctQ_i2oYxk? zjaY>W{vG3d4>XWrcA3>epHq}&&M&1w~RfJ~JHNp&WhW zVbhBOmem$>3}i?%f+*Esdm0Y!P>nugb0VRLWz`R$Pl?h=nj)xw_Nay*dH85WE=$&r zIXpU~89|h4kUbw74lhs*Kb&wdA+}|;1&@IWQ94Oe1hVH*^I;Z6_XDgCG#D7{4*<&$ J(u^QVH2@5yTgw0d literal 0 HcmV?d00001 diff --git a/assets/audio/sounds/bell.mp3 b/assets/audio/sounds/bell.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d7df1f4045cd0f9e8a05275e9a059abc5be78962 GIT binary patch literal 3972 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 b?ld2qqixc_s-eKZVA8)sN;zF1anR)3z7C#WP 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 6385a5f..681f7d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,19 @@ "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", + "expo-av": "~16.0.8", + "expo-blur": "~15.0.8", "expo-constants": "~18.0.13", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-keep-awake": "~15.0.8", + "expo-linear-gradient": "~15.0.8", "expo-linking": "~8.0.11", "expo-router": "~6.0.23", "expo-splash-screen": "~31.0.13", @@ -33,7 +37,8 @@ "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", - "react-native-worklets": "0.5.1" + "react-native-worklets": "0.5.1", + "zustand": "^5.0.11" }, "devDependencies": { "@types/react": "~19.1.0", @@ -2766,6 +2771,18 @@ } } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -6072,6 +6089,34 @@ "react-native": "*" } }, + "node_modules/expo-av": { + "version": "16.0.8", + "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-16.0.8.tgz", + "integrity": "sha512-cmVPftGR/ca7XBgs7R6ky36lF3OC0/MM/lpgX/yXqfv0jASTsh7AYX9JxHCwFmF+Z6JEB1vne9FDx4GiLcGreQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-blur": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-15.0.8.tgz", + "integrity": "sha512-rWyE1NBRZEu9WD+X+5l7gyPRszw7n12cW3IRNAb5i6KFzaBp8cxqT5oeaphJapqURvcqhkOZn2k5EtBSbsuU7w==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-constants": { "version": "18.0.13", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", @@ -6146,6 +6191,17 @@ "react": "*" } }, + "node_modules/expo-linear-gradient": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.8.tgz", + "integrity": "sha512-V2d8Wjn0VzhPHO+rrSBtcl+Fo+jUUccdlmQ6OoL9/XQB7Qk3d9lYrqKDJyccwDxmQT10JdST3Tmf2K52NLc3kw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-linking": { "version": "8.0.11", "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz", @@ -7842,6 +7898,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8835,6 +8900,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12889,6 +12966,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 04143e6..398e1f6 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,19 @@ }, "dependencies": { "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", "expo": "~54.0.33", + "expo-av": "~16.0.8", + "expo-blur": "~15.0.8", "expo-constants": "~18.0.13", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-keep-awake": "~15.0.8", + "expo-linear-gradient": "~15.0.8", "expo-linking": "~8.0.11", "expo-router": "~6.0.23", "expo-splash-screen": "~31.0.13", @@ -36,13 +40,14 @@ "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", - "react-native-worklets": "0.5.1" + "react-native-worklets": "0.5.1", + "zustand": "^5.0.11" }, "devDependencies": { "@types/react": "~19.1.0", - "typescript": "~5.9.2", "eslint": "^9.25.0", - "eslint-config-expo": "~10.0.0" + "eslint-config-expo": "~10.0.0", + "typescript": "~5.9.2" }, "private": true } diff --git a/src/features/audio/data/sounds.ts b/src/features/audio/data/sounds.ts new file mode 100644 index 0000000..df5fced --- /dev/null +++ b/src/features/audio/data/sounds.ts @@ -0,0 +1,13 @@ +import type { PhaseSound } from '../types' + +/* eslint-disable @typescript-eslint/no-require-imports */ +export const PHASE_SOUNDS: Record = { + beep_long: require('@/assets/audio/sounds/beep_long.mp3'), + beep_double: require('@/assets/audio/sounds/beep_double.mp3'), + beep_short: require('@/assets/audio/sounds/beep_short.mp3'), + bell: require('@/assets/audio/sounds/bell.mp3'), + fanfare: require('@/assets/audio/sounds/fanfare.mp3'), + count_3: require('@/assets/audio/sounds/count_3.mp3'), + count_2: require('@/assets/audio/sounds/count_2.mp3'), + count_1: require('@/assets/audio/sounds/count_1.mp3'), +} diff --git a/src/features/audio/data/tracks.ts b/src/features/audio/data/tracks.ts new file mode 100644 index 0000000..2431840 --- /dev/null +++ b/src/features/audio/data/tracks.ts @@ -0,0 +1,27 @@ +import type { MusicIntensity } from '../types' + +interface MusicTrack { + id: string + intensity: MusicIntensity + asset: number +} + +/* eslint-disable @typescript-eslint/no-require-imports */ +export const TRACKS: MusicTrack[] = [ + { + id: 'electro_high', + intensity: 'HIGH', + asset: require('@/assets/audio/music/electro_high.mp3'), + }, + { + id: 'electro_low', + intensity: 'LOW', + asset: require('@/assets/audio/music/electro_low.mp3'), + }, +] + +export function getTrack(intensity: MusicIntensity): MusicTrack { + const track = TRACKS.find((t) => t.intensity === intensity) + if (!track) throw new Error(`Track not found: ${intensity}`) + return track +} diff --git a/src/features/audio/hooks/useAudioEngine.ts b/src/features/audio/hooks/useAudioEngine.ts new file mode 100644 index 0000000..e0f29c3 --- /dev/null +++ b/src/features/audio/hooks/useAudioEngine.ts @@ -0,0 +1,173 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import { Audio } from 'expo-av' +import type { AudioEngine, MusicIntensity, PhaseSound } from '../types' +import { PHASE_SOUNDS } from '../data/sounds' +import { getTrack } from '../data/tracks' + +const FADE_STEPS = 10 + +async function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +export function useAudioEngine(): AudioEngine { + const [isLoaded, setIsLoaded] = useState(false) + const soundsRef = useRef>({}) + const currentIntensityRef = useRef('LOW') + const musicVolumeRef = useRef(0.5) + + // Configure audio session once + useEffect(() => { + Audio.setAudioModeAsync({ + playsInSilentModeIOS: true, + allowsRecordingIOS: false, + staysActiveInBackground: true, + shouldDuckAndroid: true, + playThroughEarpieceAndroid: false, + }).catch((e) => { + if (__DEV__) console.warn('[AudioEngine] Failed to configure audio session:', e) + }) + }, []) + + const preloadAll = useCallback(async () => { + try { + // Preload phase sounds + for (const [key, asset] of Object.entries(PHASE_SOUNDS)) { + const { sound } = await Audio.Sound.createAsync(asset, { + shouldPlay: false, + volume: 1.0, + }) + soundsRef.current[key] = sound + } + + // Preload music tracks + const highTrack = getTrack('HIGH') + const lowTrack = getTrack('LOW') + + const { sound: musicHigh } = await Audio.Sound.createAsync(highTrack.asset, { + shouldPlay: false, + volume: 0, + isLooping: true, + }) + soundsRef.current['music_high'] = musicHigh + + const { sound: musicLow } = await Audio.Sound.createAsync(lowTrack.asset, { + shouldPlay: false, + volume: 0, + isLooping: true, + }) + soundsRef.current['music_low'] = musicLow + + setIsLoaded(true) + if (__DEV__) console.log('[AudioEngine] All sounds preloaded') + } catch (e) { + if (__DEV__) console.warn('[AudioEngine] Preload error:', e) + } + }, []) + + const playPhaseSound = useCallback(async (sound: PhaseSound) => { + const s = soundsRef.current[sound] + if (!s) return + try { + await s.setPositionAsync(0) + await s.playAsync() + } catch (e) { + if (__DEV__) console.warn('[AudioEngine] Play error:', sound, e) + } + }, []) + + const startMusic = useCallback(async (intensity: MusicIntensity) => { + const key = intensity === 'HIGH' ? 'music_high' : 'music_low' + const s = soundsRef.current[key] + if (!s) return + try { + currentIntensityRef.current = intensity + await s.setPositionAsync(0) + await s.setVolumeAsync(musicVolumeRef.current) + await s.playAsync() + } catch (e) { + if (__DEV__) console.warn('[AudioEngine] startMusic error:', e) + } + }, []) + + const switchIntensity = useCallback(async (to: MusicIntensity) => { + const from = currentIntensityRef.current + if (from === to) return + + const outKey = from === 'HIGH' ? 'music_high' : 'music_low' + const inKey = to === 'HIGH' ? 'music_high' : 'music_low' + const outSound = soundsRef.current[outKey] + const inSound = soundsRef.current[inKey] + + if (!outSound || !inSound) return + + try { + const vol = musicVolumeRef.current + await inSound.setPositionAsync(0) + await inSound.setVolumeAsync(0) + await inSound.playAsync() + + // Crossfade + const stepMs = 500 / FADE_STEPS + for (let i = 1; i <= FADE_STEPS; i++) { + const progress = i / FADE_STEPS + await Promise.all([ + outSound.setVolumeAsync(vol * (1 - progress)), + inSound.setVolumeAsync(vol * progress), + ]) + await delay(stepMs) + } + + await outSound.stopAsync() + currentIntensityRef.current = to + } catch (e) { + if (__DEV__) console.warn('[AudioEngine] switchIntensity error:', e) + } + }, []) + + const stopMusic = useCallback(async (fadeMs: number = 500) => { + const key = currentIntensityRef.current === 'HIGH' ? 'music_high' : 'music_low' + const s = soundsRef.current[key] + if (!s) return + + try { + const stepMs = fadeMs / FADE_STEPS + for (let i = FADE_STEPS; i >= 0; i--) { + await s.setVolumeAsync(musicVolumeRef.current * (i / FADE_STEPS)) + await delay(stepMs) + } + await s.stopAsync() + } catch (e) { + if (__DEV__) console.warn('[AudioEngine] stopMusic error:', e) + } + }, []) + + const unloadAll = useCallback(async () => { + await Promise.all( + Object.values(soundsRef.current).map((s) => + s.unloadAsync().catch(() => {}) + ) + ) + soundsRef.current = {} + setIsLoaded(false) + }, []) + + // Cleanup on unmount + useEffect(() => { + return () => { + Object.values(soundsRef.current).forEach((s) => { + s.unloadAsync().catch(() => {}) + }) + } + }, []) + + return { + isLoaded, + preloadAll, + playPhaseSound, + startMusic, + switchIntensity, + stopMusic, + unloadAll, + } +} diff --git a/src/features/audio/index.ts b/src/features/audio/index.ts new file mode 100644 index 0000000..5d596ce --- /dev/null +++ b/src/features/audio/index.ts @@ -0,0 +1,8 @@ +export { useAudioEngine } from './hooks/useAudioEngine' +export type { + MusicAmbiance, + MusicIntensity, + PhaseSound, + AudioSettings, + AudioEngine, +} from './types' diff --git a/src/features/audio/types.ts b/src/features/audio/types.ts new file mode 100644 index 0000000..677df8b --- /dev/null +++ b/src/features/audio/types.ts @@ -0,0 +1,31 @@ +export type MusicAmbiance = 'ELECTRO' | 'SILENCE' +export type MusicIntensity = 'LOW' | 'HIGH' + +export type PhaseSound = + | 'beep_long' + | 'beep_double' + | 'beep_short' + | 'bell' + | 'fanfare' + | 'count_3' + | 'count_2' + | 'count_1' + +export interface AudioSettings { + musicEnabled: boolean + ambiance: MusicAmbiance + musicVolume: number + soundsEnabled: boolean + soundsVolume: number + hapticsEnabled: boolean +} + +export interface AudioEngine { + isLoaded: boolean + preloadAll: () => Promise + playPhaseSound: (sound: PhaseSound) => Promise + startMusic: (intensity: MusicIntensity) => Promise + switchIntensity: (intensity: MusicIntensity) => Promise + stopMusic: (fadeMs?: number) => Promise + unloadAll: () => Promise +} diff --git a/src/features/onboarding/components/ChoiceButton.tsx b/src/features/onboarding/components/ChoiceButton.tsx new file mode 100644 index 0000000..1502e2e --- /dev/null +++ b/src/features/onboarding/components/ChoiceButton.tsx @@ -0,0 +1,100 @@ +import { StyleSheet, View, Text, Pressable } from 'react-native' +import { Ionicons } from '@expo/vector-icons' +import { BRAND, GLASS, TEXT, BORDER, SURFACE } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { RADIUS } from '@/src/shared/constants/borderRadius' +import { SPACING } from '@/src/shared/constants/spacing' + +interface ChoiceButtonProps { + label: string + description?: string + icon: keyof typeof Ionicons.glyphMap + selected: boolean + onPress: () => void +} + +export function ChoiceButton({ + label, + description, + icon, + selected, + onPress, +}: ChoiceButtonProps) { + return ( + + + + + + + + {label} + + {description && ( + {description} + )} + + {selected && ( + + + + )} + + + ) +} + +const styles = StyleSheet.create({ + pressable: { + width: '100%', + }, + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: GLASS.FILL, + borderWidth: 1, + borderColor: GLASS.BORDER, + borderRadius: RADIUS.LG, + paddingVertical: SPACING[4], + paddingHorizontal: SPACING[4], + gap: SPACING[3], + }, + containerSelected: { + backgroundColor: SURFACE.OVERLAY_LIGHT, + borderColor: BRAND.PRIMARY, + borderWidth: 2, + }, + iconContainer: { + width: 48, + height: 48, + borderRadius: RADIUS.MD, + backgroundColor: SURFACE.OVERLAY_LIGHT, + alignItems: 'center', + justifyContent: 'center', + }, + iconContainerSelected: { + backgroundColor: SURFACE.OVERLAY_MEDIUM, + }, + textContainer: { + flex: 1, + }, + label: { + ...TYPOGRAPHY.body, + color: TEXT.PRIMARY, + }, + labelSelected: { + color: BRAND.PRIMARY, + }, + description: { + ...TYPOGRAPHY.caption, + color: TEXT.MUTED, + marginTop: 4, + }, + checkmark: { + marginLeft: SPACING[2], + }, +}) diff --git a/src/features/onboarding/components/MiniTimerDemo.tsx b/src/features/onboarding/components/MiniTimerDemo.tsx new file mode 100644 index 0000000..7649ebf --- /dev/null +++ b/src/features/onboarding/components/MiniTimerDemo.tsx @@ -0,0 +1,178 @@ +import { useEffect, useRef, useState } from 'react' +import { StyleSheet, View, Text, Animated } from 'react-native' +import { useTimerEngine } from '@/src/features/timer/hooks/useTimerEngine' +import { PHASE_COLORS, TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { RADIUS } from '@/src/shared/constants/borderRadius' +import { SPACING } from '@/src/shared/constants/spacing' + +interface MiniTimerDemoProps { + onComplete?: () => void + onPhaseChange?: (phase: string) => void + onCountdownTick?: (seconds: number) => void + autoStartDelay?: number +} + +export function MiniTimerDemo({ + onComplete, + onPhaseChange, + onCountdownTick, + autoStartDelay = 500, +}: MiniTimerDemoProps) { + const timer = useTimerEngine() + const [hasCompleted, setHasCompleted] = useState(false) + const pulseAnim = useRef(new Animated.Value(1)).current + const glowAnim = useRef(new Animated.Value(0)).current + + useEffect(() => { + // Auto-start after a short delay + const startTimeout = setTimeout(() => { + timer.start({ + workDuration: 20, + restDuration: 0, + rounds: 1, + getReadyDuration: 3, + cycles: 1, + }) + }, autoStartDelay) + + return () => clearTimeout(startTimeout) + }, [timer, autoStartDelay]) + + useEffect(() => { + // Listen for all timer events + const unsubscribe = timer.addEventListener((event) => { + switch (event.type) { + case 'SESSION_COMPLETE': + setHasCompleted(true) + onComplete?.() + break + case 'PHASE_CHANGED': + onPhaseChange?.(event.to) + break + case 'COUNTDOWN_TICK': + onCountdownTick?.(event.secondsLeft) + break + } + }) + + return unsubscribe + }, [timer, onComplete, onPhaseChange, onCountdownTick]) + + useEffect(() => { + // Pulse animation when running + if (timer.isRunning) { + Animated.loop( + Animated.sequence([ + Animated.timing(pulseAnim, { + toValue: 1.05, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(pulseAnim, { + toValue: 1, + duration: 800, + useNativeDriver: true, + }), + ]) + ).start() + } else { + pulseAnim.setValue(1) + } + }, [timer.isRunning, pulseAnim]) + + useEffect(() => { + // Glow animation + Animated.loop( + Animated.sequence([ + Animated.timing(glowAnim, { + toValue: 1, + duration: 1500, + useNativeDriver: true, + }), + Animated.timing(glowAnim, { + toValue: 0, + duration: 1500, + useNativeDriver: true, + }), + ]) + ).start() + }, [glowAnim]) + + const getPhaseText = (): string => { + if (hasCompleted) return 'DONE!' + if (timer.phase === 'GET_READY') return 'GET READY' + if (timer.phase === 'WORK') return 'GO!' + if (timer.phase === 'COMPLETE') return 'DONE!' + return '' + } + + const phaseColor = PHASE_COLORS[timer.phase] || PHASE_COLORS.IDLE + const displaySeconds = timer.secondsLeft > 0 ? timer.secondsLeft : 0 + + return ( + + + + + {displaySeconds} + + + {getPhaseText()} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: SPACING[6], + }, + timerCircle: { + width: 200, + height: 200, + borderRadius: 100, + borderWidth: 4, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.3)', + overflow: 'hidden', + }, + glowOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + borderRadius: 100, + }, + countdown: { + ...TYPOGRAPHY.timeDisplay, + fontVariant: ['tabular-nums'], + }, + phaseText: { + ...TYPOGRAPHY.label, + marginTop: SPACING[2], + letterSpacing: 3, + }, +}) diff --git a/src/features/onboarding/components/OnboardingScreen.tsx b/src/features/onboarding/components/OnboardingScreen.tsx new file mode 100644 index 0000000..dc1e2c5 --- /dev/null +++ b/src/features/onboarding/components/OnboardingScreen.tsx @@ -0,0 +1,46 @@ +import { ReactNode } from 'react' +import { StyleSheet, View, SafeAreaView } from 'react-native' +import { LinearGradient } from 'expo-linear-gradient' +import { APP_GRADIENTS } from '@/src/shared/constants/colors' +import { LAYOUT, SPACING } from '@/src/shared/constants/spacing' +import { ProgressBar } from './ProgressBar' + +interface OnboardingScreenProps { + children: ReactNode + currentStep: number + totalSteps?: number +} + +export function OnboardingScreen({ + children, + currentStep, + totalSteps = 6, +}: OnboardingScreenProps) { + return ( + + + + {children} + + + ) +} + +const styles = StyleSheet.create({ + gradient: { + flex: 1, + }, + container: { + flex: 1, + }, + content: { + flex: 1, + paddingHorizontal: LAYOUT.PAGE_HORIZONTAL, + paddingBottom: SPACING[6], + }, +}) diff --git a/src/features/onboarding/components/PaywallCard.tsx b/src/features/onboarding/components/PaywallCard.tsx new file mode 100644 index 0000000..6553604 --- /dev/null +++ b/src/features/onboarding/components/PaywallCard.tsx @@ -0,0 +1,158 @@ +import { StyleSheet, View, Text, Pressable } from 'react-native' +import { BRAND, GLASS, TEXT, SURFACE, BORDER } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { RADIUS } from '@/src/shared/constants/borderRadius' +import { SPACING } from '@/src/shared/constants/spacing' + +interface PaywallCardProps { + title: string + price: string + period: string + features: string[] + selected: boolean + onPress: () => void + badge?: string +} + +export function PaywallCard({ + title, + price, + period, + features, + selected, + onPress, + badge, +}: PaywallCardProps) { + return ( + + + {badge && ( + + {badge} + + )} + + + {title} + + + {price} + /{period} + + + + + {features.map((feature, index) => ( + + + {feature} + + ))} + + {selected && ( + + + + )} + + + ) +} + +const styles = StyleSheet.create({ + pressable: { + width: '100%', + }, + container: { + backgroundColor: GLASS.FILL_MEDIUM, + borderWidth: 1, + borderColor: GLASS.BORDER, + borderRadius: RADIUS.XL, + padding: SPACING[4], + position: 'relative', + overflow: 'hidden', + }, + containerSelected: { + backgroundColor: SURFACE.OVERLAY_MEDIUM, + borderColor: BRAND.PRIMARY, + borderWidth: 2, + }, + badge: { + position: 'absolute', + top: 0, + right: 0, + backgroundColor: BRAND.PRIMARY, + paddingHorizontal: SPACING[3], + paddingVertical: SPACING[1.5], + borderBottomLeftRadius: RADIUS.MD, + }, + badgeText: { + ...TYPOGRAPHY.overline, + color: TEXT.PRIMARY, + fontWeight: '700', + }, + header: { + marginBottom: SPACING[3], + }, + title: { + ...TYPOGRAPHY.heading, + color: TEXT.PRIMARY, + marginBottom: SPACING[2], + }, + titleSelected: { + color: BRAND.PRIMARY, + }, + priceRow: { + flexDirection: 'row', + alignItems: 'baseline', + }, + price: { + ...TYPOGRAPHY.displaySmall, + color: TEXT.PRIMARY, + fontWeight: '900', + }, + period: { + ...TYPOGRAPHY.caption, + color: TEXT.MUTED, + marginLeft: SPACING[1], + }, + divider: { + height: 1, + backgroundColor: BORDER.LIGHT, + marginBottom: SPACING[3], + }, + features: { + gap: SPACING[2], + }, + featureRow: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING[2], + }, + featureDot: { + width: 6, + height: 6, + borderRadius: 3, + backgroundColor: BRAND.PRIMARY, + }, + featureText: { + ...TYPOGRAPHY.caption, + color: TEXT.SECONDARY, + }, + selectedIndicator: { + position: 'absolute', + top: SPACING[3], + left: SPACING[3], + }, + selectedDot: { + width: 12, + height: 12, + borderRadius: 6, + backgroundColor: BRAND.PRIMARY, + shadowColor: BRAND.PRIMARY, + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.6, + shadowRadius: 6, + elevation: 3, + }, +}) diff --git a/src/features/onboarding/components/PrimaryButton.tsx b/src/features/onboarding/components/PrimaryButton.tsx new file mode 100644 index 0000000..4353078 --- /dev/null +++ b/src/features/onboarding/components/PrimaryButton.tsx @@ -0,0 +1,85 @@ +import { StyleSheet, Text, Pressable, Animated } from 'react-native' +import { BRAND, TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { RADIUS } from '@/src/shared/constants/borderRadius' + +interface PrimaryButtonProps { + title: string + onPress: () => void + disabled?: boolean +} + +export function PrimaryButton({ + title, + onPress, + disabled = false, +}: PrimaryButtonProps) { + const animatedValue = new Animated.Value(1) + + const handlePressIn = () => { + Animated.spring(animatedValue, { + toValue: 0.96, + useNativeDriver: true, + }).start() + } + + const handlePressOut = () => { + Animated.spring(animatedValue, { + toValue: 1, + friction: 3, + useNativeDriver: true, + }).start() + } + + return ( + + + + {title} + + + + ) +} + +const styles = StyleSheet.create({ + button: { + backgroundColor: BRAND.PRIMARY, + borderRadius: RADIUS['2XL'], + paddingVertical: 18, + paddingHorizontal: 32, + alignItems: 'center', + justifyContent: 'center', + shadowColor: BRAND.PRIMARY, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.4, + shadowRadius: 12, + elevation: 8, + }, + buttonDisabled: { + backgroundColor: BRAND.PRIMARY, + opacity: 0.5, + shadowOpacity: 0, + elevation: 0, + }, + text: { + ...TYPOGRAPHY.buttonMedium, + color: TEXT.PRIMARY, + }, + textDisabled: { + color: TEXT.PRIMARY, + opacity: 0.7, + }, +}) diff --git a/src/features/onboarding/components/ProgressBar.tsx b/src/features/onboarding/components/ProgressBar.tsx new file mode 100644 index 0000000..4d3e0f7 --- /dev/null +++ b/src/features/onboarding/components/ProgressBar.tsx @@ -0,0 +1,89 @@ +import { useEffect, useRef } from 'react' +import { StyleSheet, View, Animated } from 'react-native' +import { BRAND, SURFACE } from '@/src/shared/constants/colors' + +interface ProgressBarProps { + currentStep: number + totalSteps?: number +} + +export function ProgressBar({ + currentStep, + totalSteps = 6, +}: ProgressBarProps) { + const scaleAnims = useRef( + Array.from({ length: totalSteps }, () => new Animated.Value(1)) + ).current + + useEffect(() => { + // Animate the newly active dot + if (currentStep >= 0 && currentStep < totalSteps) { + Animated.sequence([ + Animated.timing(scaleAnims[currentStep], { + toValue: 1.3, + duration: 150, + useNativeDriver: true, + }), + Animated.timing(scaleAnims[currentStep], { + toValue: 1, + duration: 150, + useNativeDriver: true, + }), + ]).start() + } + }, [currentStep, totalSteps, scaleAnims]) + + return ( + + {Array.from({ length: totalSteps }).map((_, index) => { + const isActive = index === currentStep + const isCompleted = index < currentStep + + return ( + + ) + })} + + ) +} + +const DOT_SIZE = 10 +const DOT_SIZE_ACTIVE = 12 + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: 12, + paddingVertical: 16, + }, + dot: { + width: DOT_SIZE, + height: DOT_SIZE, + borderRadius: DOT_SIZE / 2, + backgroundColor: SURFACE.OVERLAY_LIGHT, + }, + dotActive: { + width: DOT_SIZE_ACTIVE, + height: DOT_SIZE_ACTIVE, + borderRadius: DOT_SIZE_ACTIVE / 2, + backgroundColor: BRAND.PRIMARY, + shadowColor: BRAND.PRIMARY, + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.8, + shadowRadius: 8, + elevation: 4, + }, + dotCompleted: { + backgroundColor: BRAND.PRIMARY, + }, +}) diff --git a/src/features/onboarding/components/index.ts b/src/features/onboarding/components/index.ts new file mode 100644 index 0000000..e5fae39 --- /dev/null +++ b/src/features/onboarding/components/index.ts @@ -0,0 +1,6 @@ +export { OnboardingScreen } from './OnboardingScreen' +export { ProgressBar } from './ProgressBar' +export { PrimaryButton } from './PrimaryButton' +export { ChoiceButton } from './ChoiceButton' +export { MiniTimerDemo } from './MiniTimerDemo' +export { PaywallCard } from './PaywallCard' diff --git a/src/features/onboarding/data/barriers.ts b/src/features/onboarding/data/barriers.ts new file mode 100644 index 0000000..8bc33c4 --- /dev/null +++ b/src/features/onboarding/data/barriers.ts @@ -0,0 +1,15 @@ +import type { Barrier } from '../types' + +export interface BarrierOption { + id: Barrier + label: string + description: string + icon: string // Ionicons name +} + +export const BARRIERS: BarrierOption[] = [ + { id: 'time', label: 'Le temps', description: 'Je n\'ai pas assez de temps', icon: 'time-outline' }, + { id: 'motivation', label: 'La motivation', description: 'Je n\'arrive pas à rester motivé(e)', icon: 'flash-outline' }, + { id: 'knowledge', label: 'Le savoir', description: 'Je ne sais pas quoi faire', icon: 'book-outline' }, + { id: 'gym', label: 'La salle', description: 'Je n\'ai pas accès à une salle', icon: 'barbell-outline' }, +] diff --git a/src/features/onboarding/data/goals.ts b/src/features/onboarding/data/goals.ts new file mode 100644 index 0000000..8664f27 --- /dev/null +++ b/src/features/onboarding/data/goals.ts @@ -0,0 +1,15 @@ +import type { Goal } from '../types' + +export interface GoalOption { + id: Goal + label: string + description: string + icon: string +} + +export const GOALS: GoalOption[] = [ + { id: 'weight_loss', label: 'Perte de poids', description: 'Brûler des graisses efficacement', icon: 'flame-outline' }, + { id: 'cardio', label: 'Cardio', description: 'Améliorer mon endurance', icon: 'heart-outline' }, + { id: 'strength', label: 'Force', description: 'Développer ma musculature', icon: 'barbell-outline' }, + { id: 'wellness', label: 'Bien-être', description: 'Me sentir mieux dans mon corps', icon: 'happy-outline' }, +] diff --git a/src/features/onboarding/data/index.ts b/src/features/onboarding/data/index.ts new file mode 100644 index 0000000..6612251 --- /dev/null +++ b/src/features/onboarding/data/index.ts @@ -0,0 +1,5 @@ +// Barrel export for onboarding data + +export { BARRIERS, type BarrierOption } from './barriers' +export { GOALS, type GoalOption } from './goals' +export { LEVELS, type LevelOption } from './levels' diff --git a/src/features/onboarding/data/levels.ts b/src/features/onboarding/data/levels.ts new file mode 100644 index 0000000..8a3e687 --- /dev/null +++ b/src/features/onboarding/data/levels.ts @@ -0,0 +1,14 @@ +import type { Level } from '../types' + +export interface LevelOption { + id: Level + label: string + description: string + icon: string +} + +export const LEVELS: LevelOption[] = [ + { id: 'beginner', label: 'Débutant', description: 'Je commence le sport', icon: 'leaf-outline' }, + { id: 'intermediate', label: 'Intermédiaire', description: 'Je fais du sport régulièrement', icon: 'fitness-outline' }, + { id: 'advanced', label: 'Avancé', description: 'Je suis très actif(ve)', icon: 'trophy-outline' }, +] diff --git a/src/features/onboarding/hooks/useOnboarding.ts b/src/features/onboarding/hooks/useOnboarding.ts new file mode 100644 index 0000000..b5a8b57 --- /dev/null +++ b/src/features/onboarding/hooks/useOnboarding.ts @@ -0,0 +1,137 @@ +import { create } from 'zustand' +import { + createJSONStorage, + persist, + type StateStorage, +} from 'zustand/middleware' +import AsyncStorage from '@react-native-async-storage/async-storage' +import type { + Barrier, + Frequency, + Goal, + Level, + OnboardingData, + OnboardingState, +} from '../types' + +const STORAGE_KEY_COMPLETE = 'tabatago_onboarding_complete' +const STORAGE_KEY_DATA = 'tabatago_onboarding_data' + +// Custom storage that uses AsyncStorage +const onboardingStorage: StateStorage = { + getItem: async (name: string): Promise => { + return await AsyncStorage.getItem(name) + }, + setItem: async (name: string, value: string): Promise => { + await AsyncStorage.setItem(name, value) + }, + removeItem: async (name: string): Promise => { + await AsyncStorage.removeItem(name) + }, +} + +const initialData: OnboardingData = { + barrier: null, + level: null, + goal: null, + frequency: null, +} + +interface OnboardingActions { + setStep: (step: number) => void + nextStep: () => void + prevStep: () => void + setData: (data: Partial) => void + setBarrier: (barrier: Barrier) => void + setLevel: (level: Level) => void + setGoal: (goal: Goal) => void + setFrequency: (frequency: Frequency) => void + completeOnboarding: () => void + resetOnboarding: () => void +} + +type OnboardingStore = OnboardingState & OnboardingActions + +export const useOnboarding = create()( + persist( + (set, get) => ({ + // Initial state + currentStep: 0, + isOnboardingComplete: false, + data: initialData, + + // Actions + setStep: (step: number) => { + set({ currentStep: step }) + }, + + nextStep: () => { + const { currentStep } = get() + set({ currentStep: currentStep + 1 }) + }, + + prevStep: () => { + const { currentStep } = get() + if (currentStep > 0) { + set({ currentStep: currentStep - 1 }) + } + }, + + setData: (data: Partial) => { + set((state) => ({ + data: { ...state.data, ...data }, + })) + }, + + setBarrier: (barrier: Barrier) => { + set((state) => ({ + data: { ...state.data, barrier }, + })) + }, + + setLevel: (level: Level) => { + set((state) => ({ + data: { ...state.data, level }, + })) + }, + + setGoal: (goal: Goal) => { + set((state) => ({ + data: { ...state.data, goal }, + })) + }, + + setFrequency: (frequency: Frequency) => { + set((state) => ({ + data: { ...state.data, frequency }, + })) + }, + + completeOnboarding: () => { + set({ isOnboardingComplete: true }) + }, + + resetOnboarding: () => { + set({ + currentStep: 0, + isOnboardingComplete: false, + data: initialData, + }) + }, + }), + { + name: STORAGE_KEY_DATA, + storage: createJSONStorage(() => onboardingStorage), + partialize: (state) => ({ + isOnboardingComplete: state.isOnboardingComplete, + data: state.data, + }), + } + ) +) + +// Selector hooks for better performance +export const useOnboardingStep = () => useOnboarding((state) => state.currentStep) +export const useIsOnboardingComplete = () => + useOnboarding((state) => state.isOnboardingComplete) +export const useOnboardingData = () => useOnboarding((state) => state.data) diff --git a/src/features/onboarding/index.ts b/src/features/onboarding/index.ts new file mode 100644 index 0000000..5feec6b --- /dev/null +++ b/src/features/onboarding/index.ts @@ -0,0 +1,26 @@ +// Types +export * from './types' + +// Hooks +export { useOnboarding, useOnboardingStep, useIsOnboardingComplete, useOnboardingData } from './hooks/useOnboarding' + +// Components +export { OnboardingScreen } from './components/OnboardingScreen' +export { PrimaryButton } from './components/PrimaryButton' +export { ChoiceButton } from './components/ChoiceButton' +export { PaywallCard } from './components/PaywallCard' +export { MiniTimerDemo } from './components/MiniTimerDemo' +export { ProgressBar } from './components/ProgressBar' + +// Screens +export { Screen1Problem } from './screens/Screen1Problem' +export { Screen2Empathy } from './screens/Screen2Empathy' +export { Screen3Solution } from './screens/Screen3Solution' +export { Screen4WowMoment } from './screens/Screen4WowMoment' +export { Screen5Personalization } from './screens/Screen5Personalization' +export { Screen6Paywall } from './screens/Screen6Paywall' + +// Data +export { BARRIERS } from './data/barriers' +export { LEVELS } from './data/levels' +export { GOALS } from './data/goals' diff --git a/src/features/onboarding/screens/Screen1Problem.tsx b/src/features/onboarding/screens/Screen1Problem.tsx new file mode 100644 index 0000000..530c458 --- /dev/null +++ b/src/features/onboarding/screens/Screen1Problem.tsx @@ -0,0 +1,165 @@ +import { useEffect, useRef } from 'react' +import { StyleSheet, View, Text, Animated } from 'react-native' +import { Ionicons } from '@expo/vector-icons' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { PrimaryButton } from '../components/PrimaryButton' +import { BRAND, TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' + +interface Screen1ProblemProps { + onNext: () => void +} + +export function Screen1Problem({ onNext }: Screen1ProblemProps) { + const clockScale = useRef(new Animated.Value(1)).current + const clockRotation = useRef(new Animated.Value(0)).current + const opacityAnim = useRef(new Animated.Value(0)).current + const translateYAnim = useRef(new Animated.Value(20)).current + + useEffect(() => { + // Entrance animation + Animated.parallel([ + Animated.timing(opacityAnim, { + toValue: 1, + duration: 600, + useNativeDriver: true, + }), + Animated.timing(translateYAnim, { + toValue: 0, + duration: 600, + useNativeDriver: true, + }), + ]).start() + + // Clock pulse animation + const pulseAnimation = Animated.loop( + Animated.sequence([ + Animated.timing(clockScale, { + toValue: 1.1, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(clockScale, { + toValue: 1, + duration: 800, + useNativeDriver: true, + }), + ]) + ) + + // Clock rotation animation + const rotationAnimation = Animated.loop( + Animated.timing(clockRotation, { + toValue: 1, + duration: 4000, + useNativeDriver: true, + }) + ) + + pulseAnimation.start() + rotationAnimation.start() + + return () => { + pulseAnimation.stop() + rotationAnimation.stop() + } + }, [clockScale, clockRotation, opacityAnim, translateYAnim]) + + const rotationInterpolate = clockRotation.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }) + + return ( + + + {/* Clock Icon Animation */} + + + + + + + + + + + + {/* Title and Subtitle */} + + Tu n'as pas 1 heure pour t'entrainer ? + Ni 30 minutes ? Ni meme 10 ? + + + {/* Spacer */} + + + {/* Continue Button */} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: SPACING[4], + }, + iconContainer: { + position: 'relative', + marginBottom: SPACING[10], + }, + clockCircle: { + width: 140, + height: 140, + borderRadius: 70, + backgroundColor: 'rgba(249, 115, 22, 0.1)', + alignItems: 'center', + justifyContent: 'center', + borderWidth: 2, + borderColor: 'rgba(249, 115, 22, 0.3)', + }, + crossMark: { + position: 'absolute', + bottom: -5, + right: -5, + backgroundColor: 'rgba(239, 68, 68, 0.2)', + borderRadius: 20, + padding: SPACING[1], + }, + textContainer: { + alignItems: 'center', + marginBottom: SPACING[8], + }, + title: { + ...TYPOGRAPHY.displayLarge, + color: TEXT.PRIMARY, + textAlign: 'center', + marginBottom: SPACING[4], + }, + subtitle: { + ...TYPOGRAPHY.heading, + color: TEXT.MUTED, + textAlign: 'center', + }, + spacer: { + flex: 1, + }, +}) diff --git a/src/features/onboarding/screens/Screen2Empathy.tsx b/src/features/onboarding/screens/Screen2Empathy.tsx new file mode 100644 index 0000000..755125b --- /dev/null +++ b/src/features/onboarding/screens/Screen2Empathy.tsx @@ -0,0 +1,104 @@ +import { useState, useEffect, useRef } from 'react' +import { StyleSheet, View, Text, Animated } from 'react-native' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { ChoiceButton } from '../components/ChoiceButton' +import { useOnboarding } from '../hooks/useOnboarding' +import { BARRIERS } from '../data/barriers' +import { TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' +import type { Barrier } from '../types' + +interface Screen2EmpathyProps { + onNext: () => void +} + +export function Screen2Empathy({ onNext }: Screen2EmpathyProps) { + const [selectedBarrier, setSelectedBarrier] = useState(null) + const { setBarrier } = useOnboarding() + const opacityAnim = useRef(new Animated.Value(0)).current + const translateYAnim = useRef(new Animated.Value(20)).current + + useEffect(() => { + // Entrance animation + Animated.parallel([ + Animated.timing(opacityAnim, { + toValue: 1, + duration: 600, + useNativeDriver: true, + }), + Animated.timing(translateYAnim, { + toValue: 0, + duration: 600, + useNativeDriver: true, + }), + ]).start() + }, [opacityAnim, translateYAnim]) + + const handleBarrierSelect = (barrier: Barrier) => { + setSelectedBarrier(barrier) + setBarrier(barrier) + // Auto-advance after selection + setTimeout(() => { + onNext() + }, 300) + } + + return ( + + + {/* Title */} + + + Qu'est-ce qui t'empeche de t'entrainer ? + + + + {/* Choice Buttons */} + + {BARRIERS.map((barrier, index) => ( + + handleBarrierSelect(barrier.id)} + /> + + ))} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: SPACING[8], + }, + titleContainer: { + marginBottom: SPACING[8], + paddingHorizontal: SPACING[2], + }, + title: { + ...TYPOGRAPHY.heading, + color: TEXT.PRIMARY, + textAlign: 'center', + }, + choicesContainer: { + gap: SPACING[3], + }, + choiceWrapper: { + width: '100%', + }, +}) diff --git a/src/features/onboarding/screens/Screen3Solution.tsx b/src/features/onboarding/screens/Screen3Solution.tsx new file mode 100644 index 0000000..49b8c1e --- /dev/null +++ b/src/features/onboarding/screens/Screen3Solution.tsx @@ -0,0 +1,251 @@ +import { useEffect, useRef } from 'react' +import { StyleSheet, View, Text, Animated } from 'react-native' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { PrimaryButton } from '../components/PrimaryButton' +import { BRAND, TEXT, SURFACE, PHASE_COLORS } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' +import { RADIUS } from '@/src/shared/constants/borderRadius' + +interface Screen3SolutionProps { + onNext: () => void +} + +const TABATA_ROUNDS = 8 +const WORK_DURATION = 20 +const REST_DURATION = 10 + +export function Screen3Solution({ onNext }: Screen3SolutionProps) { + const opacityAnim = useRef(new Animated.Value(0)).current + const translateYAnim = useRef(new Animated.Value(20)).current + const activeRound = useRef(new Animated.Value(0)).current + const pulseAnim = useRef(new Animated.Value(1)).current + + useEffect(() => { + // Entrance animation + Animated.parallel([ + Animated.timing(opacityAnim, { + toValue: 1, + duration: 600, + useNativeDriver: true, + }), + Animated.timing(translateYAnim, { + toValue: 0, + duration: 600, + useNativeDriver: true, + }), + ]).start() + + // Round cycling animation + const roundAnimation = Animated.loop( + Animated.sequence([ + ...Array.from({ length: TABATA_ROUNDS }, (_, i) => + Animated.timing(activeRound, { + toValue: i + 1, + duration: 800, + useNativeDriver: false, + }) + ), + Animated.timing(activeRound, { + toValue: 0, + duration: 500, + useNativeDriver: false, + }), + ]) + ) + + // Pulse animation for the "4 minutes" text + const pulseAnimation = Animated.loop( + Animated.sequence([ + Animated.timing(pulseAnim, { + toValue: 1.05, + duration: 600, + useNativeDriver: true, + }), + Animated.timing(pulseAnim, { + toValue: 1, + duration: 600, + useNativeDriver: true, + }), + ]) + ) + + roundAnimation.start() + pulseAnimation.start() + + return () => { + roundAnimation.stop() + pulseAnimation.stop() + } + }, [activeRound, opacityAnim, translateYAnim, pulseAnim]) + + const renderTabataTimeline = () => { + return ( + + {Array.from({ length: TABATA_ROUNDS }, (_, index) => { + const isWork = index % 2 === 0 + const baseColor = isWork ? PHASE_COLORS.WORK : PHASE_COLORS.REST + + return ( + + + {isWork ? WORK_DURATION : REST_DURATION}s + + + ) + })} + + ) + } + + return ( + + + {/* Animated Title */} + + 4 minutes + + + {/* Subtitle */} + Vraiment transformatrices. + + {/* Tabata Timeline Animation */} + + {renderTabataTimeline()} + + {/* Legend */} + + + + 20s travail + + + + 10s repos + + x 8 rounds + + + + {/* Scientific Explanation */} + + + Protocole scientifique HIIT = resultats max en temps min + + + + {/* Spacer */} + + + {/* Continue Button */} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + paddingTop: SPACING[10], + }, + titleContainer: { + marginBottom: SPACING[2], + }, + title: { + ...TYPOGRAPHY.brandTitle, + color: BRAND.PRIMARY, + textAlign: 'center', + }, + subtitle: { + ...TYPOGRAPHY.heading, + color: TEXT.SECONDARY, + textAlign: 'center', + marginBottom: SPACING[8], + }, + animationContainer: { + width: '100%', + paddingVertical: SPACING[6], + paddingHorizontal: SPACING[4], + }, + timelineContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + gap: SPACING[2], + marginBottom: SPACING[4], + }, + timelineBlock: { + width: 40, + height: 40, + borderRadius: RADIUS.MD, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: SURFACE.OVERLAY_LIGHT, + }, + timelineText: { + ...TYPOGRAPHY.overline, + color: TEXT.PRIMARY, + fontWeight: '700', + }, + legendContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: SPACING[4], + flexWrap: 'wrap', + }, + legendItem: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING[1], + }, + legendDot: { + width: 10, + height: 10, + borderRadius: 5, + }, + legendText: { + ...TYPOGRAPHY.caption, + color: TEXT.MUTED, + }, + explanationContainer: { + paddingHorizontal: SPACING[4], + marginTop: SPACING[4], + }, + explanationText: { + ...TYPOGRAPHY.body, + color: TEXT.TERTIARY, + textAlign: 'center', + lineHeight: 26, + }, + spacer: { + flex: 1, + }, +}) diff --git a/src/features/onboarding/screens/Screen4WowMoment.tsx b/src/features/onboarding/screens/Screen4WowMoment.tsx new file mode 100644 index 0000000..0e20ca0 --- /dev/null +++ b/src/features/onboarding/screens/Screen4WowMoment.tsx @@ -0,0 +1,149 @@ +import { useState, useEffect, useCallback } from 'react' +import { StyleSheet, View, Text, Animated } from 'react-native' +import * as Haptics from 'expo-haptics' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { PrimaryButton } from '../components/PrimaryButton' +import { MiniTimerDemo } from '../components/MiniTimerDemo' +import { useOnboarding } from '../hooks/useOnboarding' +import { TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' + +export function Screen4WowMoment() { + const nextStep = useOnboarding((state) => state.nextStep) + const [isComplete, setIsComplete] = useState(false) + const [currentPhase, setCurrentPhase] = useState('IDLE') + const fadeAnim = useState(new Animated.Value(0))[0] + + // Fade in animation for the button when complete + useEffect(() => { + if (isComplete) { + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success) + Animated.timing(fadeAnim, { + toValue: 1, + duration: 400, + useNativeDriver: true, + }).start() + } + }, [isComplete, fadeAnim]) + + const getInstructionText = (): string => { + if (isComplete) { + return 'Bravo ! Tu viens de completer ton premier mini-Tabata.' + } + if (currentPhase === 'GET_READY') { + return 'Prepare-toi... Le timer va bientot commencer !' + } + if (currentPhase === 'WORK') { + return 'Donne tout ! 20 secondes, c est parti !' + } + return 'Un mini-Tabata de 20 secondes. Juste pour sentir.' + } + + const handlePhaseChange = useCallback((phase: string) => { + setCurrentPhase(phase) + if (phase === 'WORK') { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium) + } else if (phase === 'GET_READY') { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) + } + }, []) + + const handleCountdownTick = useCallback((seconds: number) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) + }, []) + + const handleComplete = useCallback(() => { + setIsComplete(true) + }, []) + + const handleContinue = () => { + nextStep() + } + + return ( + + + {/* Title Section */} + + Essaie maintenant + 20 secondes. Juste pour sentir. + + + {/* Timer Demo - The "Wow" Moment */} + + + + + {/* Instruction Text */} + + {getInstructionText()} + + + {/* Continue Button - Only visible after completion */} + + {isComplete ? ( + + + + ) : ( + + )} + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'space-between', + paddingTop: SPACING[10], + }, + header: { + alignItems: 'center', + paddingHorizontal: SPACING[4], + }, + title: { + ...TYPOGRAPHY.displayLarge, + color: TEXT.PRIMARY, + textAlign: 'center', + marginBottom: SPACING[3], + }, + subtitle: { + ...TYPOGRAPHY.body, + color: TEXT.SECONDARY, + textAlign: 'center', + }, + timerContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + instructionContainer: { + paddingHorizontal: SPACING[6], + paddingVertical: SPACING[4], + alignItems: 'center', + }, + instructionText: { + ...TYPOGRAPHY.caption, + color: TEXT.TERTIARY, + textAlign: 'center', + lineHeight: 24, + }, + buttonContainer: { + paddingHorizontal: SPACING[4], + paddingBottom: SPACING[4], + minHeight: 70, + alignItems: 'center', + justifyContent: 'center', + }, + placeholderButton: { + height: 54, + }, +}) diff --git a/src/features/onboarding/screens/Screen5Personalization.tsx b/src/features/onboarding/screens/Screen5Personalization.tsx new file mode 100644 index 0000000..1fb7ff1 --- /dev/null +++ b/src/features/onboarding/screens/Screen5Personalization.tsx @@ -0,0 +1,186 @@ +import { useState } from 'react' +import { StyleSheet, View, Text, ScrollView } from 'react-native' +import { Ionicons } from '@expo/vector-icons' +import { useOnboarding } from '../hooks/useOnboarding' +import { LEVELS } from '../data/levels' +import { GOALS } from '../data/goals' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { ChoiceButton } from '../components/ChoiceButton' +import { PrimaryButton } from '../components/PrimaryButton' +import { BRAND, TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' +import type { Level, Goal, Frequency } from '../types' + +interface Screen5PersonalizationProps { + onNext: () => void +} + +interface FrequencyOption { + id: Frequency + label: string + description: string +} + +const FREQUENCIES: FrequencyOption[] = [ + { id: 2, label: '2x/semaine', description: 'Démarrage en douceur' }, + { id: 3, label: '3x/semaine', description: 'Rythme equilibre' }, + { id: 5, label: '5x/semaine', description: 'Entrainement intensif' }, +] + +export function Screen5Personalization({ onNext }: Screen5PersonalizationProps) { + const { data, setLevel, setGoal, setFrequency } = useOnboarding() + + // Local state for selections + const [selectedLevel, setSelectedLevel] = useState(data.level) + const [selectedGoal, setSelectedGoal] = useState(data.goal) + const [selectedFrequency, setSelectedFrequency] = useState(data.frequency) + + const handleLevelSelect = (level: Level) => { + setSelectedLevel(level) + setLevel(level) + } + + const handleGoalSelect = (goal: Goal) => { + setSelectedGoal(goal) + setGoal(goal) + } + + const handleFrequencySelect = (frequency: Frequency) => { + setSelectedFrequency(frequency) + setFrequency(frequency) + } + + const handleContinue = () => { + onNext() + } + + const isFormComplete = selectedLevel && selectedGoal && selectedFrequency + + return ( + + + + Personnalise ton experience + + Dis-nous en plus sur toi pour un programme sur mesure + + + + {/* Level Section */} + + + + Niveau + + + {LEVELS.map((level) => ( + handleLevelSelect(level.id)} + /> + ))} + + + + {/* Goal Section */} + + + + Objectif + + + {GOALS.map((goal) => ( + handleGoalSelect(goal.id)} + /> + ))} + + + + {/* Frequency Section */} + + + + Frequence + + + {FREQUENCIES.map((freq) => ( + handleFrequencySelect(freq.id)} + /> + ))} + + + + + + + + + ) +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + }, + scrollContent: { + paddingBottom: SPACING[6], + }, + header: { + marginBottom: SPACING[6], + }, + title: { + ...TYPOGRAPHY.heading, + color: TEXT.PRIMARY, + marginBottom: SPACING[2], + }, + subtitle: { + ...TYPOGRAPHY.caption, + color: TEXT.MUTED, + }, + section: { + marginBottom: SPACING[6], + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING[2], + marginBottom: SPACING[3], + }, + sectionTitle: { + ...TYPOGRAPHY.label, + color: TEXT.SECONDARY, + textTransform: 'uppercase', + letterSpacing: 1, + }, + optionsContainer: { + gap: SPACING[3], + }, + footer: { + paddingTop: SPACING[4], + }, +}) diff --git a/src/features/onboarding/screens/Screen6Paywall.tsx b/src/features/onboarding/screens/Screen6Paywall.tsx new file mode 100644 index 0000000..a2b5bc9 --- /dev/null +++ b/src/features/onboarding/screens/Screen6Paywall.tsx @@ -0,0 +1,234 @@ +import { useState } from 'react' +import { StyleSheet, View, Text, ScrollView, Pressable } from 'react-native' +import { useOnboarding } from '../hooks/useOnboarding' +import { OnboardingScreen } from '../components/OnboardingScreen' +import { PaywallCard } from '../components/PaywallCard' +import { BRAND, TEXT } from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SPACING } from '@/src/shared/constants/spacing' + +interface Screen6PaywallProps { + onComplete: () => void +} + +interface PlanOption { + id: 'trial' | 'monthly' | 'annual' + title: string + price: string + period: string + features: string[] + badge?: string +} + +const PLANS: PlanOption[] = [ + { + id: 'trial', + title: 'Essai gratuit', + price: '0', + period: '7 jours', + features: [ + 'Acces complet pendant 7 jours', + 'Tous les programmes debloques', + 'Annule a tout moment', + ], + }, + { + id: 'monthly', + title: 'Mensuel', + price: '4.99', + period: 'mois', + features: [ + 'Acces illimite', + 'Tous les programmes', + 'Support prioritaire', + 'Nouvelles fonctionnalites', + ], + }, + { + id: 'annual', + title: 'Annuel', + price: '29.99', + period: 'an', + badge: 'Economise 50%', + features: [ + 'Acces illimite', + 'Tous les programmes', + 'Support prioritaire', + 'Nouvelles fonctionnalites', + 'Entrainements exclusifs', + ], + }, +] + +export function Screen6Paywall({ onComplete }: Screen6PaywallProps) { + const { completeOnboarding } = useOnboarding() + const [selectedPlan, setSelectedPlan] = useState<'trial' | 'monthly' | 'annual'>('annual') + const [isLoading, setIsLoading] = useState(false) + + const handlePlanSelect = (planId: 'trial' | 'monthly' | 'annual') => { + setSelectedPlan(planId) + } + + const handlePurchase = async () => { + setIsLoading(true) + + try { + // Mock RevenueCat purchase in dev mode + // In production, this would call the actual RevenueCat SDK + await mockPurchase(selectedPlan) + + // Complete onboarding and navigate to home + completeOnboarding() + onComplete() + } catch (error) { + console.error('Purchase failed:', error) + // In production, show error to user + } finally { + setIsLoading(false) + } + } + + const handleSkip = () => { + // Skip paywall and go to home + completeOnboarding() + onComplete() + } + + return ( + + + + Debloque ton potentiel + + 7 jours gratuits, annule quand tu veux + + + + + {PLANS.map((plan) => ( + handlePlanSelect(plan.id)} + /> + ))} + + + + + Garantie satisfait ou rembourse sous 30 jours + + + + + + + + {isLoading ? 'Traitement...' : selectedPlan === 'trial' ? 'Essayer gratuitement' : "S'abonner"} + + + + + + Continuer sans abonnement + + + + + ) +} + +// Mock RevenueCat purchase function for dev mode +async function mockPurchase(planId: string): Promise { + // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // Simulate successful purchase + console.log(`[MOCK] Purchase successful for plan: ${planId}`) + + // In production with RevenueCat: + // const { customerInfo } = await Purchases.purchasePackage(package) + // if (customerInfo.entitlements.active['pro']) { ... } +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + }, + scrollContent: { + paddingBottom: SPACING[6], + }, + header: { + marginBottom: SPACING[6], + alignItems: 'center', + }, + title: { + ...TYPOGRAPHY.heading, + color: TEXT.PRIMARY, + marginBottom: SPACING[2], + textAlign: 'center', + }, + subtitle: { + ...TYPOGRAPHY.caption, + color: TEXT.MUTED, + textAlign: 'center', + }, + plansContainer: { + gap: SPACING[4], + }, + guarantee: { + marginTop: SPACING[6], + alignItems: 'center', + }, + guaranteeText: { + ...TYPOGRAPHY.caption, + color: TEXT.HINT, + textAlign: 'center', + }, + footer: { + paddingTop: SPACING[4], + }, + continueButton: { + backgroundColor: BRAND.PRIMARY, + borderRadius: 28, + paddingVertical: 18, + paddingHorizontal: 32, + alignItems: 'center', + justifyContent: 'center', + shadowColor: BRAND.PRIMARY, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.4, + shadowRadius: 12, + elevation: 8, + }, + buttonLoading: { + opacity: 0.7, + }, + continueButtonText: { + ...TYPOGRAPHY.buttonMedium, + color: TEXT.PRIMARY, + }, + skipButton: { + marginTop: SPACING[4], + paddingVertical: SPACING[2], + alignItems: 'center', + }, + skipButtonText: { + ...TYPOGRAPHY.caption, + color: TEXT.HINT, + textDecorationLine: 'underline', + }, +}) diff --git a/src/features/onboarding/screens/index.ts b/src/features/onboarding/screens/index.ts new file mode 100644 index 0000000..cdb24e2 --- /dev/null +++ b/src/features/onboarding/screens/index.ts @@ -0,0 +1,2 @@ +export { Screen5Personalization } from './Screen5Personalization' +export { Screen6Paywall } from './Screen6Paywall' diff --git a/src/features/onboarding/types.ts b/src/features/onboarding/types.ts new file mode 100644 index 0000000..80c73a2 --- /dev/null +++ b/src/features/onboarding/types.ts @@ -0,0 +1,20 @@ +export type Barrier = 'time' | 'motivation' | 'knowledge' | 'gym' + +export type Level = 'beginner' | 'intermediate' | 'advanced' + +export type Goal = 'weight_loss' | 'cardio' | 'strength' | 'wellness' + +export type Frequency = 2 | 3 | 5 + +export interface OnboardingData { + barrier: Barrier | null + level: Level | null + goal: Goal | null + frequency: Frequency | null +} + +export interface OnboardingState { + currentStep: number + isOnboardingComplete: boolean + data: OnboardingData +} diff --git a/src/features/timer/components/TimerControls.tsx b/src/features/timer/components/TimerControls.tsx index 530a814..46448a2 100644 --- a/src/features/timer/components/TimerControls.tsx +++ b/src/features/timer/components/TimerControls.tsx @@ -1,6 +1,10 @@ import { Pressable, StyleSheet, View } from 'react-native' import Ionicons from '@expo/vector-icons/Ionicons' +import { SURFACE, TEXT } from '@/src/shared/constants/colors' +import { RADIUS } from '@/src/shared/constants/borderRadius' +import { LAYOUT } from '@/src/shared/constants/spacing' + interface TimerControlsProps { isRunning: boolean isPaused: boolean @@ -24,7 +28,7 @@ export function TimerControls({ style={({ pressed }) => [styles.button, pressed && styles.pressed]} onPress={onStop} > - + @@ -46,7 +50,7 @@ export function TimerControls({ style={({ pressed }) => [styles.button, pressed && styles.pressed]} onPress={onSkip} > - + ) @@ -57,23 +61,23 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', - gap: 32, + gap: LAYOUT.CONTROLS_GAP, }, button: { width: 56, height: 56, - borderRadius: 28, - backgroundColor: 'rgba(255, 255, 255, 0.15)', + borderRadius: RADIUS['2XL'], + backgroundColor: SURFACE.OVERLAY_MEDIUM, alignItems: 'center', justifyContent: 'center', }, mainButton: { width: 72, height: 72, - borderRadius: 36, - backgroundColor: 'rgba(255, 255, 255, 0.25)', + borderRadius: RADIUS['4XL'], + backgroundColor: SURFACE.OVERLAY_STRONG, }, pressed: { - opacity: 0.6, + transform: [{ scale: 0.92 }], }, }) diff --git a/src/features/timer/components/TimerDisplay.tsx b/src/features/timer/components/TimerDisplay.tsx index aad6f7b..09a6b12 100644 --- a/src/features/timer/components/TimerDisplay.tsx +++ b/src/features/timer/components/TimerDisplay.tsx @@ -1,22 +1,38 @@ import { useEffect, useRef, useState } from 'react' import { Animated, - Easing, Pressable, StyleSheet, Text, View, } from 'react-native' +import { LinearGradient } from 'expo-linear-gradient' +import { BlurView } from 'expo-blur' import { StatusBar } from 'expo-status-bar' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { PHASE_COLORS } from '@/src/shared/constants/colors' +import { + PHASE_GRADIENTS, + ACCENT, + BRAND, + SURFACE, + TEXT, + BORDER, + APP_GRADIENTS, + GLASS, +} from '@/src/shared/constants/colors' +import { TYPOGRAPHY } from '@/src/shared/constants/typography' +import { SHADOW, TEXT_SHADOW } from '@/src/shared/constants/shadows' +import { RADIUS } from '@/src/shared/constants/borderRadius' +import { LAYOUT, SPACING } from '@/src/shared/constants/spacing' +import { SPRING, ANIMATION } from '@/src/shared/constants/animations' import { formatTime } from '@/src/shared/utils/formatTime' -import type { TimerConfig, TimerState } from '../types' +import type { TimerConfig, TimerPhase, TimerState } from '../types' import { TimerControls } from './TimerControls' -const PHASE_LABELS: Record = { +const PHASE_LABELS: Record = { + IDLE: '', GET_READY: 'PRÉPARE-TOI', - WORK: 'TRAVAIL', + WORK: 'GO !', REST: 'REPOS', COMPLETE: 'TERMINÉ !', } @@ -45,16 +61,11 @@ export function TimerDisplay({ onSkip, }: TimerDisplayProps) { const insets = useSafeAreaInsets() + const top = insets.top || 20 + const bottom = insets.bottom || 20 if (state.phase === 'IDLE') { - return ( - - ) + return } if (state.phase === 'COMPLETE') { @@ -63,8 +74,8 @@ export function TimerDisplay({ totalElapsedSeconds={state.totalElapsedSeconds} totalRounds={state.totalRounds} onStop={onStop} - topInset={insets.top} - bottomInset={insets.bottom} + top={top} + bottom={bottom} /> ) } @@ -78,85 +89,151 @@ export function TimerDisplay({ onResume={onResume} onStop={onStop} onSkip={onSkip} - topInset={insets.top} - bottomInset={insets.bottom} + top={top} + bottom={bottom} /> ) } -// --- IDLE view --- +// ────────────────────────────────────────────────────── +// IDLE — Start screen +// ────────────────────────────────────────────────────── function IdleView({ config, onStart, - topInset, - bottomInset, + top, + bottom, }: { config: TimerConfig onStart: () => void - topInset: number - bottomInset: number + top: number + bottom: number }) { + const glowAnim = useRef(new Animated.Value(0)).current + + useEffect(() => { + Animated.loop( + Animated.sequence([ + Animated.timing(glowAnim, { + toValue: 1, + ...ANIMATION.BREATH_HALF, + }), + Animated.timing(glowAnim, { + toValue: 0, + ...ANIMATION.BREATH_HALF, + }), + ]) + ).start() + }, []) + + const glowOpacity = glowAnim.interpolate({ + inputRange: [0, 1], + outputRange: [0.15, 0.4], + }) + + const glowScale = glowAnim.interpolate({ + inputRange: [0, 1], + outputRange: [1, 1.12], + }) + return ( - - TABATAGO + TABATA + GO - - {config.workDuration}s travail / {config.restDuration}s repos - - - {config.rounds} rounds - + + + + + - [ - styles.startButton, - pressed && styles.startButtonPressed, - ]} - onPress={onStart} - > - START - + + + [ + styles.startButton, + pressed && styles.buttonPressed, + ]} + onPress={onStart} + > + START + + + + ) +} + +function ConfigBadge({ label, sub }: { label: string; sub: string }) { + return ( + + {label} + {sub} ) } -// --- COMPLETE view --- +// ────────────────────────────────────────────────────── +// COMPLETE — Victory screen +// ────────────────────────────────────────────────────── function CompleteView({ totalElapsedSeconds, totalRounds, onStop, - topInset, - bottomInset, + top, + bottom, }: { totalElapsedSeconds: number totalRounds: number onStop: () => void - topInset: number - bottomInset: number + top: number + bottom: number }) { + const scaleAnim = useRef(new Animated.Value(0.5)).current + const opacityAnim = useRef(new Animated.Value(0)).current + + useEffect(() => { + Animated.parallel([ + Animated.spring(scaleAnim, { + toValue: 1, + ...SPRING.BOUNCY, + useNativeDriver: true, + }), + Animated.timing(opacityAnim, ANIMATION.FADE_IN), + ]).start() + }, []) + return ( - - + + 🔥 TERMINÉ ! {formatTime(totalElapsedSeconds)} @@ -166,18 +243,20 @@ function CompleteView({ [ styles.doneButton, - pressed && styles.startButtonPressed, + pressed && styles.buttonPressed, ]} onPress={onStop} > Terminer - - + + ) } -// --- ACTIVE view (GET_READY, WORK, REST) --- +// ────────────────────────────────────────────────────── +// ACTIVE — Countdown with native blur toolbar +// ────────────────────────────────────────────────────── function ActiveView({ state, @@ -187,8 +266,8 @@ function ActiveView({ onResume, onStop, onSkip, - topInset, - bottomInset, + top, + bottom, }: { state: TimerState exerciseName: string @@ -197,59 +276,36 @@ function ActiveView({ onResume: () => void onStop: () => void onSkip: () => void - topInset: number - bottomInset: number + top: number + bottom: number }) { - // --- Background color transition --- - const [prevColor, setPrevColor] = useState(PHASE_COLORS.IDLE) + const gradient = PHASE_GRADIENTS[state.phase] ?? PHASE_GRADIENTS.IDLE + + // --- Gradient crossfade --- + const [prevGradient, setPrevGradient] = useState(PHASE_GRADIENTS.IDLE) const fadeAnim = useRef(new Animated.Value(1)).current useEffect(() => { - const targetColor = PHASE_COLORS[state.phase] fadeAnim.setValue(0) - Animated.timing(fadeAnim, { - toValue: 1, - duration: 600, - easing: Easing.inOut(Easing.ease), - useNativeDriver: false, - }).start(() => { - setPrevColor(targetColor) + Animated.timing(fadeAnim, ANIMATION.GRADIENT_CROSSFADE).start(() => { + setPrevGradient(gradient) }) }, [state.phase]) - const backgroundColor = fadeAnim.interpolate({ - inputRange: [0, 1], - outputRange: [prevColor, PHASE_COLORS[state.phase]], - }) - // --- Countdown pulse --- const pulseAnim = useRef(new Animated.Value(1)).current useEffect(() => { if (state.isRunning) { Animated.sequence([ - Animated.timing(pulseAnim, { - toValue: 1.05, - duration: 80, - useNativeDriver: true, - }), - Animated.timing(pulseAnim, { - toValue: 1, - duration: 80, - useNativeDriver: true, - }), + Animated.timing(pulseAnim, ANIMATION.PULSE_UP), + Animated.timing(pulseAnim, ANIMATION.PULSE_DOWN), ]).start() } }, [state.secondsLeft]) - // --- Progress --- - const totalPhaseDuration = - state.phase === 'GET_READY' - ? 0 - : (state.currentRound - 1) / state.totalRounds const isLastSeconds = state.secondsLeft <= 3 && state.secondsLeft > 0 - // Top info line const topLabel = state.phase === 'GET_READY' ? exerciseName @@ -258,86 +314,99 @@ function ActiveView({ : exerciseName return ( - + + ) } +// ────────────────────────────────────────────────────── +// Styles +// ────────────────────────────────────────────────────── + const styles = StyleSheet.create({ screen: { flex: 1, @@ -348,94 +417,137 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', justifyContent: 'center', - gap: 24, }, idleTitle: { - fontSize: 40, - fontWeight: '900', - color: '#FFFFFF', - letterSpacing: 6, + ...TYPOGRAPHY.brandTitle, + color: ACCENT.ORANGE, + ...TEXT_SHADOW.BRAND, + }, + idleSubtitle: { + ...TYPOGRAPHY.displaySmall, + color: ACCENT.WHITE, + marginTop: -6, }, configSummary: { + marginTop: SPACING[10], + }, + configRow: { + flexDirection: 'row', + gap: SPACING[3], + }, + configBadge: { + backgroundColor: SURFACE.OVERLAY_LIGHT, + borderRadius: RADIUS.LG, + paddingVertical: SPACING[3], + paddingHorizontal: SPACING[5], alignItems: 'center', - gap: 4, - marginTop: 16, + borderWidth: 1, + borderColor: BORDER.LIGHT, }, - configLine: { - fontSize: 18, - color: 'rgba(255, 255, 255, 0.7)', - fontWeight: '500', + configBadgeLabel: { + ...TYPOGRAPHY.heading, + color: ACCENT.WHITE, }, - startButton: { - width: 140, - height: 140, - borderRadius: 70, - backgroundColor: '#F97316', + configBadgeSub: { + ...TYPOGRAPHY.overline, + color: TEXT.MUTED, + marginTop: 2, + }, + startButtonContainer: { + marginTop: SPACING[14], alignItems: 'center', justifyContent: 'center', - marginTop: 40, }, - startButtonPressed: { - opacity: 0.7, + startButtonGlow: { + position: 'absolute', + width: 172, + height: 172, + borderRadius: 86, + backgroundColor: ACCENT.ORANGE, + }, + startButton: { + width: 150, + height: 150, + borderRadius: 75, + backgroundColor: ACCENT.ORANGE, + alignItems: 'center', + justifyContent: 'center', + ...SHADOW.BRAND_GLOW, + }, + buttonPressed: { + transform: [{ scale: 0.95 }], }, startButtonText: { - fontSize: 28, - fontWeight: '900', - color: '#FFFFFF', - letterSpacing: 3, + ...TYPOGRAPHY.buttonHero, + color: ACCENT.WHITE, }, // --- COMPLETE --- + completeContent: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + gap: SPACING[2], + }, + completeEmoji: { + fontSize: 72, + marginBottom: SPACING[2], + }, completeTitle: { - fontSize: 40, - fontWeight: '900', - color: '#FFFFFF', + ...TYPOGRAPHY.displayLarge, + color: ACCENT.WHITE, }, completeTime: { - fontSize: 64, - fontWeight: '900', - color: '#FFFFFF', - fontVariant: ['tabular-nums'], - marginTop: 8, + ...TYPOGRAPHY.timeDisplay, + color: ACCENT.WHITE, + ...TEXT_SHADOW.WHITE_MEDIUM, }, completeRounds: { - fontSize: 18, - color: 'rgba(255, 255, 255, 0.8)', - fontWeight: '500', + ...TYPOGRAPHY.body, + color: TEXT.TERTIARY, + fontWeight: '600', }, doneButton: { - paddingHorizontal: 48, - paddingVertical: 16, - borderRadius: 32, - backgroundColor: 'rgba(255, 255, 255, 0.25)', - marginTop: 40, + marginTop: SPACING[10], + paddingHorizontal: SPACING[12], + paddingVertical: SPACING[4], + borderRadius: RADIUS['3XL'], + backgroundColor: SURFACE.OVERLAY_MEDIUM, + borderWidth: 1, + borderColor: BORDER.STRONG, }, doneButtonText: { - fontSize: 20, - fontWeight: '700', - color: '#FFFFFF', + ...TYPOGRAPHY.buttonMedium, + color: ACCENT.WHITE, }, // --- ACTIVE --- + activeContent: { + flex: 1, + }, topZone: { - paddingHorizontal: 24, - paddingTop: 16, - paddingBottom: 12, + paddingHorizontal: LAYOUT.PAGE_HORIZONTAL, + paddingTop: SPACING[3], + paddingBottom: SPACING[2], flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, exerciseName: { - fontSize: 18, - fontWeight: '600', - color: 'rgba(255, 255, 255, 0.9)', + ...TYPOGRAPHY.body, + color: TEXT.SECONDARY, flex: 1, }, - roundIndicator: { - fontSize: 16, - fontWeight: '700', - color: 'rgba(255, 255, 255, 0.8)', - marginLeft: 12, + roundBadge: { + backgroundColor: SURFACE.OVERLAY_MEDIUM, + borderRadius: RADIUS.MD, + paddingVertical: SPACING[1.5], + paddingHorizontal: SPACING[3.5], + marginLeft: SPACING[3], + }, + roundBadgeText: { + ...TYPOGRAPHY.label, + color: ACCENT.WHITE, }, centerZone: { @@ -444,68 +556,60 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, phaseLabel: { - fontSize: 22, - fontWeight: '700', - color: 'rgba(255, 255, 255, 0.8)', - letterSpacing: 3, - marginBottom: 8, + ...TYPOGRAPHY.heading, + color: TEXT.TERTIARY, + letterSpacing: 6, + marginBottom: SPACING[1], }, countdown: { - fontSize: 120, - fontWeight: '900', - color: '#FFFFFF', - fontVariant: ['tabular-nums'], + ...TYPOGRAPHY.countdown, + color: ACCENT.WHITE, + ...TEXT_SHADOW.WHITE_SOFT, }, countdownFlash: { - color: '#EF4444', + color: ACCENT.RED_HOT, + ...TEXT_SHADOW.DANGER, + }, + pausedContainer: { + marginTop: SPACING[4], + paddingVertical: SPACING[2], + paddingHorizontal: SPACING[6], + borderRadius: RADIUS.XL, + backgroundColor: SURFACE.SCRIM, }, pausedLabel: { - fontSize: 18, - fontWeight: '700', - color: 'rgba(255, 255, 255, 0.6)', - letterSpacing: 4, - marginTop: 12, + ...TYPOGRAPHY.caption, + fontWeight: '800', + color: TEXT.MUTED, + letterSpacing: 6, }, - progressZone: { - paddingHorizontal: 24, - gap: 12, - marginBottom: 8, - }, - progressTrack: { - height: 4, - backgroundColor: 'rgba(255, 255, 255, 0.2)', - borderRadius: 2, - overflow: 'hidden', - }, - progressFill: { - height: '100%', - backgroundColor: 'rgba(255, 255, 255, 0.8)', - borderRadius: 2, + // --- Bottom toolbar (native blur) --- + bottomBar: { + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: BORDER.MEDIUM, + paddingTop: SPACING[4], + gap: SPACING[5], }, roundDots: { flexDirection: 'row', justifyContent: 'center', - gap: 8, + gap: SPACING[2.5], }, dot: { width: 8, height: 8, - borderRadius: 4, - backgroundColor: 'rgba(255, 255, 255, 0.25)', + borderRadius: RADIUS.XS, + backgroundColor: SURFACE.OVERLAY_MEDIUM, }, dotFilled: { - backgroundColor: 'rgba(255, 255, 255, 0.9)', + backgroundColor: TEXT.SECONDARY, }, dotActive: { - backgroundColor: '#FFFFFF', - width: 10, - height: 10, - borderRadius: 5, - }, - - controlsZone: { - paddingBottom: 24, - paddingTop: 16, + backgroundColor: ACCENT.WHITE, + width: 12, + height: 12, + borderRadius: SPACING[1.5], + ...SHADOW.WHITE_GLOW, }, }) diff --git a/src/features/timer/hooks/useTimerEngine.ts b/src/features/timer/hooks/useTimerEngine.ts index 8a64f10..933d135 100644 --- a/src/features/timer/hooks/useTimerEngine.ts +++ b/src/features/timer/hooks/useTimerEngine.ts @@ -117,6 +117,9 @@ export function useTimerEngine(): TimerEngine { const duration = getDurationForPhase(nextPhase, configRef.current) if (duration > 0) { startCountdown(duration) + } else { + // Phase with 0 duration - immediately advance to next phase + advancePhase() } } diff --git a/src/shared/components/GlassView.tsx b/src/shared/components/GlassView.tsx new file mode 100644 index 0000000..5341336 --- /dev/null +++ b/src/shared/components/GlassView.tsx @@ -0,0 +1,32 @@ +import { StyleSheet, View, type ViewProps } from 'react-native' +import { BlurView } from 'expo-blur' +import { GLASS } from '../constants/colors' + +interface GlassViewProps extends ViewProps { + intensity?: number + children: React.ReactNode +} + +export function GlassView({ + intensity = GLASS.BLUR_MEDIUM, + style, + children, + ...rest +}: GlassViewProps) { + return ( + + + {children} + + ) +} + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + backgroundColor: GLASS.FILL, + borderWidth: 0.5, + borderColor: GLASS.BORDER, + borderTopColor: GLASS.BORDER_TOP, + }, +}) diff --git a/src/shared/components/Typography.tsx b/src/shared/components/Typography.tsx new file mode 100644 index 0000000..4f5d1b6 --- /dev/null +++ b/src/shared/components/Typography.tsx @@ -0,0 +1,25 @@ +import { Text, type TextProps } from 'react-native' +import { TYPOGRAPHY } from '../constants/typography' +import { TEXT } from '../constants/colors' + +type TypographyVariant = keyof typeof TYPOGRAPHY + +interface TypographyProps extends TextProps { + variant: TypographyVariant + color?: string + children: React.ReactNode +} + +export function Typography({ + variant, + color = TEXT.PRIMARY, + style, + children, + ...rest +}: TypographyProps) { + return ( + + {children} + + ) +} diff --git a/src/shared/constants/animations.ts b/src/shared/constants/animations.ts new file mode 100644 index 0000000..fa3c215 --- /dev/null +++ b/src/shared/constants/animations.ts @@ -0,0 +1,51 @@ +import { Easing } from 'react-native' + +export const DURATION = { + SNAP: 60, + QUICK: 120, + FAST: 300, + NORMAL: 400, + SLOW: 600, + XSLOW: 1000, + BREATH: 1800, +} as const + +export const EASING = { + STANDARD: Easing.inOut(Easing.ease), + DECELERATE: Easing.out(Easing.ease), + LINEAR: Easing.linear, +} as const + +export const SPRING = { + BOUNCY: { tension: 50, friction: 7 }, +} as const + +export const ANIMATION = { + PULSE_UP: { + toValue: 1.08, + duration: DURATION.SNAP, + useNativeDriver: true, + }, + PULSE_DOWN: { + toValue: 1, + duration: DURATION.QUICK, + easing: EASING.DECELERATE, + useNativeDriver: true, + }, + GRADIENT_CROSSFADE: { + toValue: 1, + duration: DURATION.SLOW, + easing: EASING.STANDARD, + useNativeDriver: true, + }, + FADE_IN: { + toValue: 1, + duration: DURATION.NORMAL, + useNativeDriver: true, + }, + BREATH_HALF: { + duration: DURATION.BREATH, + easing: EASING.STANDARD, + useNativeDriver: false, + }, +} as const diff --git a/src/shared/constants/borderRadius.ts b/src/shared/constants/borderRadius.ts new file mode 100644 index 0000000..609595f --- /dev/null +++ b/src/shared/constants/borderRadius.ts @@ -0,0 +1,11 @@ +export const RADIUS = { + XS: 4, + SM: 6, + MD: 12, + LG: 16, + XL: 20, + '2XL': 28, + '3XL': 32, + '4XL': 36, + FULL: 9999, +} as const diff --git a/src/shared/constants/colors.ts b/src/shared/constants/colors.ts index 5473b40..2ef4ec5 100644 --- a/src/shared/constants/colors.ts +++ b/src/shared/constants/colors.ts @@ -5,3 +5,72 @@ export const PHASE_COLORS = { REST: '#3B82F6', COMPLETE: '#22C55E', } as const + +export const PHASE_GRADIENTS = { + IDLE: ['#0A0A14', '#12101F', '#1E1E2E'] as const, + GET_READY: ['#451A03', '#92400E', '#D97706'] as const, + WORK: ['#450A0A', '#991B1B', '#EA580C'] as const, + REST: ['#0C1929', '#1E3A5F', '#2563EB'] as const, + COMPLETE: ['#052E16', '#166534', '#16A34A'] as const, +} as const + +export const ACCENT = { + ORANGE: '#F97316', + ORANGE_GLOW: '#FB923C', + RED_HOT: '#EF4444', + GOLD: '#FBBF24', + WHITE: '#FFFFFF', + WHITE_DIM: 'rgba(255, 255, 255, 0.6)', + WHITE_FAINT: 'rgba(255, 255, 255, 0.15)', +} as const + +export const BRAND = { + PRIMARY: '#F97316', + PRIMARY_LIGHT: '#FB923C', + SECONDARY: '#FBBF24', + DANGER: '#EF4444', + SUCCESS: '#22C55E', + INFO: '#3B82F6', +} as const + +export const SURFACE = { + BASE: '#0A0A14', + RAISED: '#12101F', + ELEVATED: '#1E1E2E', + OVERLAY_LIGHT: 'rgba(255, 255, 255, 0.08)', + OVERLAY_MEDIUM: 'rgba(255, 255, 255, 0.15)', + OVERLAY_STRONG: 'rgba(255, 255, 255, 0.25)', + SCRIM: 'rgba(0, 0, 0, 0.3)', +} as const + +export const TEXT = { + PRIMARY: '#FFFFFF', + SECONDARY: 'rgba(255, 255, 255, 0.85)', + TERTIARY: 'rgba(255, 255, 255, 0.75)', + MUTED: 'rgba(255, 255, 255, 0.6)', + HINT: 'rgba(255, 255, 255, 0.45)', + DISABLED: 'rgba(255, 255, 255, 0.15)', +} as const + +export const BORDER = { + SUBTLE: 'rgba(255, 255, 255, 0.05)', + LIGHT: 'rgba(255, 255, 255, 0.06)', + MEDIUM: 'rgba(255, 255, 255, 0.15)', + STRONG: 'rgba(255, 255, 255, 0.3)', +} as const + +export const APP_GRADIENTS = { + HOME: ['#0A0A14', '#1A0E2E', '#2D1810'] as const, +} as const + +export const GLASS = { + FILL: 'rgba(255, 255, 255, 0.03)', + FILL_MEDIUM: 'rgba(255, 255, 255, 0.06)', + FILL_STRONG: 'rgba(255, 255, 255, 0.10)', + BORDER: 'rgba(255, 255, 255, 0.08)', + BORDER_TOP: 'rgba(255, 255, 255, 0.15)', + BLUR_LIGHT: 15, + BLUR_MEDIUM: 25, + BLUR_HEAVY: 40, + BLUR_ATMOSPHERE: 60, +} as const diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts new file mode 100644 index 0000000..ea19544 --- /dev/null +++ b/src/shared/constants/index.ts @@ -0,0 +1,16 @@ +export { + PHASE_COLORS, + PHASE_GRADIENTS, + ACCENT, + BRAND, + SURFACE, + TEXT, + BORDER, + APP_GRADIENTS, + GLASS, +} from './colors' +export { TYPOGRAPHY } from './typography' +export { SPACING, LAYOUT } from './spacing' +export { SHADOW, TEXT_SHADOW } from './shadows' +export { RADIUS } from './borderRadius' +export { DURATION, EASING, SPRING, ANIMATION } from './animations' diff --git a/src/shared/constants/shadows.ts b/src/shared/constants/shadows.ts new file mode 100644 index 0000000..f9fd811 --- /dev/null +++ b/src/shared/constants/shadows.ts @@ -0,0 +1,44 @@ +import type { TextStyle, ViewStyle } from 'react-native' + +export const SHADOW = { + BRAND_GLOW: { + shadowColor: '#F97316', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.5, + shadowRadius: 20, + elevation: 12, + } as ViewStyle, + + WHITE_GLOW: { + shadowColor: '#FFFFFF', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.8, + shadowRadius: 6, + } as ViewStyle, +} as const + +export const TEXT_SHADOW = { + BRAND: { + textShadowColor: 'rgba(249, 115, 22, 0.5)', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 30, + } as TextStyle, + + WHITE_SOFT: { + textShadowColor: 'rgba(255, 255, 255, 0.25)', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 30, + } as TextStyle, + + WHITE_MEDIUM: { + textShadowColor: 'rgba(255, 255, 255, 0.3)', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 20, + } as TextStyle, + + DANGER: { + textShadowColor: 'rgba(239, 68, 68, 0.6)', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 40, + } as TextStyle, +} as const diff --git a/src/shared/constants/spacing.ts b/src/shared/constants/spacing.ts new file mode 100644 index 0000000..add388c --- /dev/null +++ b/src/shared/constants/spacing.ts @@ -0,0 +1,25 @@ +export const SPACING = { + 0: 0, + 0.5: 2, + 1: 4, + 1.5: 6, + 2: 8, + 2.5: 10, + 3: 12, + 3.5: 14, + 4: 16, + 5: 20, + 6: 24, + 8: 32, + 10: 40, + 12: 48, + 14: 56, + 15: 60, +} as const + +export const LAYOUT = { + PAGE_HORIZONTAL: 24, + SECTION_GAP: 40, + INLINE_GAP: 16, + CONTROLS_GAP: 32, +} as const diff --git a/src/shared/constants/typography.ts b/src/shared/constants/typography.ts new file mode 100644 index 0000000..5745a1c --- /dev/null +++ b/src/shared/constants/typography.ts @@ -0,0 +1,73 @@ +import type { TextStyle } from 'react-native' + +export const TYPOGRAPHY = { + countdown: { + fontSize: 140, + fontWeight: '900', + fontVariant: ['tabular-nums'], + } as TextStyle, + + timeDisplay: { + fontSize: 72, + fontWeight: '900', + fontVariant: ['tabular-nums'], + } as TextStyle, + + brandTitle: { + fontSize: 56, + fontWeight: '900', + letterSpacing: 12, + } as TextStyle, + + displayLarge: { + fontSize: 42, + fontWeight: '900', + letterSpacing: 4, + } as TextStyle, + + displaySmall: { + fontSize: 32, + fontWeight: '900', + letterSpacing: 20, + } as TextStyle, + + buttonHero: { + fontSize: 30, + fontWeight: '900', + letterSpacing: 5, + } as TextStyle, + + heading: { + fontSize: 24, + fontWeight: '800', + } as TextStyle, + + buttonMedium: { + fontSize: 20, + fontWeight: '700', + letterSpacing: 2, + } as TextStyle, + + body: { + fontSize: 18, + fontWeight: '700', + } as TextStyle, + + caption: { + fontSize: 16, + fontWeight: '500', + } as TextStyle, + + label: { + fontSize: 15, + fontWeight: '800', + letterSpacing: 1, + } as TextStyle, + + overline: { + fontSize: 11, + fontWeight: '600', + textTransform: 'uppercase', + letterSpacing: 1, + } as TextStyle, +} as const