Transforming LifeLeveling's Shell Calendar
WEEK 13
Felipe de Souza
12/1/2025
This week’s focus for our LifeLeveling capstone was getting closer to what we actually promise users: a habit system that really lives on a calendar, not just in Figma. My goal was to take our “Create Reminder” work from last week and start surfacing those reminders back into the Calendar screen in a way that feels clean, dependable, and on-brand with the rest of the app.
Framing the Problem
Right now, our calendar UI looks great, but it’s mostly a shell. We can create reminders and store them in Firestore, but the Day and Month views don’t know anything about those reminders yet.
The UX target is clear from our Figma boards:
Month view: a traditional grid where each day shows a compact summary of that day’s habits.
Day view: a focused list for a single date showing an icon, reminder title, and a row of checkboxes.
If a reminder repeats (for example, every 6 hours), the user should see four checkboxes for that day (24 ÷ 6) so they can check off each occurrence.
“How do we pull a user’s reminders out of Firestore and render them in the Day view in a way that respects repeat rules and fits our visual style?”
Data & Firestore: Getting the Right Reminders for a Day
The first step was to give the UI a reliable way to ask, “What reminders exist for this specific date?”
Inside FirestoreRepository, I added a new helper, getRemindersForDay(date, logger), which:
Grabs the current user’s id (and bails out with a log if you’re not signed in).
Converts the selected LocalDate into a start-of-day and end-of-day range using the device’s time zone.
Queries the user’s reminders subcollection for all documents with a dueAt timestamp between those two values.
Maps each document into our Reminders data class, copying the document id into reminderId for future updates.
UI Layer: DailyRemindersList & DailyReminderRow
With the repo in place, I created a pair of reusable composables in CustomDisplayComponents.kt:
DailyRemindersList(date: LocalDate, repo, logger)
Uses LaunchedEffect(date) so whenever the selected date changes, we automatically re-load reminders.
Tracks isLoading and a local reminders list.
While loading, it shows a small inline CircularProgressIndicator so the user sees something happening.
If there are no reminders, it shows a simple, on-brand empty-state message (“No reminders for this day yet.”) in our standard text style.
Otherwise, it renders a column of DailyReminderRow items.
DailyReminderRow(reminder: Reminders, logger)
Left side: the reminder icon and title. I added a helper, iconResForName, that maps an iconName saved in Firestore (like "water_drop" or "med_bottle") to the correct drawable, falling back to a bell icon if we don’t recognize the name.
Right side: a dynamic row of checkboxes.
To decide how many checkboxes to show, I introduced calculateDailySlots(reminder):
If the reminder is “every N hours,” we use 24 / N slots (so 4 for every 6 hours).
For day/week/month-based repeats we currently use a simple mapping to N slots, which we can refine later.
One-off or basic reminders default to a single checkbox.
The checkboxes themselves use our existing CustomCheckbox component so they visually match the rest of the app. For now, checking a box simply toggles local state and logs the interaction. In a future pass, those clicks will hook into a setReminderCompleted flow so we can track streaks and completion history.
Reusing Patterns: Level & Health Displays
Earlier in the project I refactored our Level & XP and Health displays to pull live data from Firestore instead of the old TestUser placeholder. Those patterns shaped how I approached reminders:
Both LevelAndProgress and HealthDisplay now fetch the current user through the repository, build a UI-friendly model, and render the result with a small loading state.
DailyRemindersList follows the same idea: rely on the repo for data, keep the UI focused on presentation and interaction.
This makes the code feel more consistent across screens and lowers the mental overhead when we come back later to add features like streak calculations or health-driven reminder suggestions.
Obstacles, Bugs, and Lessons Learned
It wouldn’t be a real dev week without some head-scratching moments:
Accidentally nesting composables: I originally pasted DailyRemindersList and DailyReminderRow inside the EquipmentDisplay composable. That meant they were scoped as local functions, so IntelliJ immediately complained with unresolved reference errors for DailyReminderRow, calculateDailySlots, and iconResForName.
Fix: move all reminder-related functions to top level, like LevelAndProgress, so the rest of the app (and each composable) can see them.
Type mismatch: Any vs Int: Our Reminders model stores some count fields (like timesPerHour or timesPerDay) in a way that effectively comes back from Firestore as Any?. When calculateDailySlots tried to treat them as Int, Kotlin was not amused: “Argument type mismatch: actual type is Any, but Int was expected.”
Fix: I added a small helper that safely converts Any? to Int by handling Int, Long, Double, String, and defaulting to 0 when needed. It’s a tiny function, but it keeps the crashy edge cases away while we refine the data model.
Missing repository helper: DailyRemindersList called repo.getRemindersForDay(...) before that method existed. This sounds obvious in hindsight, but while juggling multiple files (Calendar, custom components, the repo, etc.) it’s easy to “future-tense” a method and forget to wire it up.
Fix: implement the helper in FirestoreRepository, then re-sync the imports. Once that landed, the unresolved reference errors went away and the whole flow started to make sense.
Explore
Discover my journey as a software engineer.
© 2025. All rights reserved.