Pomodoro Timer
Pomodoro Timer is a cross-platform productivity app built with Flutter and deployed to the web via Firebase Hosting. Users configure a session by picking a duration and assigning a category (Work, Break, Learning, Health, Fitness), then run a full-screen countdown with pause, resume, and stop controls. Completed sessions are persisted to Firestore under the signed-in user's account. The dashboard aggregates session history into stat cards, a donut chart, a per-category composition bar, and a two-week activity heatmap. The app works offline — the timer runs locally, and categories show hardcoded defaults until the user signs in with Google OAuth.
Source Code & Demo
Key Features
- Full-screen countdown with pause / resume / stop
- Custom categories with color picker
- Dashboard: stat cards, donut chart, composition bar
- Two-week activity heatmap with time-of-day bucketing
- Google OAuth via Firebase Auth — guest mode works without sign-in
Code Highlights & Design Choices
A naive timer decrements a counter by 1 each second, but periodic callbacks don't fire with millisecond precision — small drift compounds over long sessions. Instead, the controller stores a deadline timestamp at start (and recomputes it on resume), then each tick reads the real wall-clock difference. The clamp handles the rare case where DateTime.now() overshoots the deadline by a few milliseconds.
The activity heatmap groups sessions by a composite key: date + time block (09, 12, 15, 18, 21). Building the map in a single fold over the session list keeps the logic O(n) and the widget purely derived from its input — no extra state or controllers needed.
Process & Workflow
I built the app feature-by-feature: scaffold and Firebase setup first, then the timer countdown, categories CRUD, and finally the dashboard. A UI overhaul pass followed to introduce a consistent dark color system and redesign each screen. Working in Flutter for the first time meant spending time understanding widget composition and state lifting before the architecture settled.
Challenges & Solutions
Flutter's widget tree makes it easy to scatter state across components, but the timer needs to stay alive while the user navigates between tabs.
Lifted TimerController to HomeShell — the parent of all tabs — and passed it down as a constructor argument. This gives every tab a single source of truth for timer state without reaching for an external state management library.
Deleting a category would orphan historical sessions, breaking dashboard aggregations that join on categoryId.
Denormalized categoryName onto each session document at write time. The dashboard falls back to 'Uncategorized' and grey when no matching category doc exists, keeping sessions immutable and queries simple.
Outcomes & Impact
The app is deployed and in active personal use. The dashboard gives a clear picture of how focus time is distributed across categories over any rolling window, which was the original goal. The heatmap in particular surfaced patterns (e.g., late-evening sessions clustering on weekdays) that weren't obvious from raw session counts.
What I Learned
This was my first Flutter project, so the biggest gain was understanding Flutter's widget composition model and how to design a widget tree that keeps state in the right place. I also learned how to deploy a Flutter app to the web via Firebase Hosting, structure Firestore data for dashboard-style aggregation queries, and handle the Firebase initialization quirk specific to Flutter Web (firebase_core owns init — the CDN snippet must not be present).