Skip to Content

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

  1. Le cycle : 25 min travail → 5 min pause → répéter (le grand cycle 4×+ pause longue : bonus).
  2. Contrôles : démarrer, mettre en pause, abandonner (avec les états qui vont bien).
  3. La notification : « ⏰ Pomodoro terminé — pause ! » même app en arrière-plan/tuée.
  4. L’écran reste allumé pendant une session (posé sur le bureau).
  5. 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 (scheduleNotificationAsync avec 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)

  1. La machine d’état en lib/ + ses tests Jest (oui, AVANT l’UI — 30 min).
  2. L’écran : anneau + contrôles, le cycle tourne app ouverte.
  3. La notification de fin planifiée/annulée — testée app TUÉE.
  4. Pause/reprise exacte + keep-awake + feedback.
  5. 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).