Projet Niv. 0 — Pomodoro
Le brief : un minuteur Pomodoro (25 min de travail / 5 min de pause), un écran, zéro persistance obligatoire. L’app la plus simple possible qui soit VRAIMENT utile — et installée ce soir.
Ce que vous allez pratiquer
Le timer honnête (endsAt — ch. 6.3), les notifications locales (ch. 9.2), useKeepAwake (ch. 9.5), le TimerRing réutilisé (ch. 10.3), et le circuit complet build → APK → téléphone (P12). Chapitres liés : 6.3, 9.2, 9.5, 10.3, 12.3.
Les features
- Le cycle : 25 min travail → 5 min pause → répéter (le grand cycle 4×+ pause longue : bonus).
- Contrôles : démarrer, mettre en pause, abandonner (avec les états qui vont bien).
- La notification : « ⏰ Pomodoro terminé — pause ! » même app en arrière-plan/tuée.
- L’écran reste allumé pendant une session (posé sur le bureau).
- Feedback : haptic au démarrage, fanfare douce à la fin (lib/feedback pattern).
L’architecture recommandée
- La machine d’état (le cœur — écrivez-la EN PREMIER, dans lib/) :
type PomodoroPhase =
| { name: 'idle' }
| { name: 'working'; endsAt: number; cycleCount: number }
| { name: 'paused'; remainingMs: number; cycleCount: number } // ← la pause capture le restant
| { name: 'break'; endsAt: number; cycleCount: number }La subtilité qui vaut le projet : pause ≠ arrière-plan. La pause capture remainingMs
(le temps s’arrête) ; l’arrière-plan ne capture rien (le temps continue — endsAt fait foi).
Deux concepts, deux comportements — les confondre est LE bug des apps Pomodoro.
- La notification de fin se PLANIFIE au démarrage de la phase (
scheduleNotificationAsyncavec trigger date = endsAt) et s’ANNULE si pause/abandon — le système sonne même app tuée, sans aucun code qui « attend ». - Un seul écran, le TimerRing au centre, Zustand pour la phase (les contrôles et l’anneau le partagent).
Les jalons (= vos commits)
- La machine d’état en lib/ + ses tests Jest (oui, AVANT l’UI — 30 min).
- L’écran : anneau + contrôles, le cycle tourne app ouverte.
- La notification de fin planifiée/annulée — testée app TUÉE.
- Pause/reprise exacte + keep-awake + feedback.
- APK sur le téléphone (le rituel du ch. 12.3).
Critères de réussite
- Un cycle complet sonne à l’heure EXACTE, app tuée, téléphone verrouillé
- 3 min d’arrière-plan pendant une session : le temps a continué (pas de cadeau)
- La pause, elle, gèle vraiment le temps (remainingMs capturé)
- Abandonner annule la notification planifiée (pas de sonnerie fantôme)
- L’écran ne s’éteint pas pendant une session — et s’éteint normalement hors session
- L’APK tourne sur votre téléphone, utilisé pour de vrai une journée
Pièges connus
- La notification « dans 25 min » se planifie par date absolue (endsAt), pas par compte à rebours relatif re-calculé — sinon pause et reprise la désynchronisent.
useKeepAwake()s’active PAR ÉCRAN — conditionnez-le à la phase active (un composant<KeepAwakeWhenRunning/>monté seulement en working/break est élégant).- Testez le passage minuit/changement d’heure ? Non — sur-ingénierie pour un Pomodoro. Le discernement fait partie de l’exercice.
💡 L’upgrade d’un soir de plus : les réglages (durées configurables — le store settings du ch. 6.4 se recopie en 10 min) et le compteur de pomodoros du jour (AsyncStorage suffit — pas de SQLite pour UN entier quotidien).