A habit tracker with a "Pop Press" comic look — halftone dots, thick panel borders, Anton/Work Sans type. One Kotlin Multiplatform codebase, native on Android, iOS, and Desktop (JVM). Local-first: no backend, no accounts, no network calls — everything lives in one JSON blob on-device.
Port of the single-file prototype DOTDAY.html; Domain.kt mirrors its logic near line-for-line.
- Today tab — the day's due habits as a checklist; tap to log/unlog, with a confetti "burst" animation and streak copy on completion.
- Habits tab — create/edit/archive/delete habits: name, category, color, custom weekday schedule (
sched()renders "Mon/Wed/Fri" etc.), start date. - Insights tab — completion
rate()over a date range,currentStreak()/longestStreak()per habit, week-by-week navigation (weekOf,weekOff). - Settings tab — theme (
auto/dark/light), week-start day (Mon/Sun), "calm mode" (reduced motion/intensity), demo-data seeding (loadDemo), wipe all data, export backup (JSON — share sheet on Android/iOS,~/dotday-backup.jsonon Desktop). - Toasts, confirm dialogs, and burst/confetti overlays are app-wide, driven by
UiStaterather than per-screen. - Responsive nav: bottom bar on narrow widths (≤600dp), top nav bar otherwise — same screens, same state.
composeApp/src/commonMain/kotlin/com/dotday/app/
domain/Domain.kt pure logic: Habit, isDue, currentStreak, longestStreak, rate, weekOf, Copy (all user-facing strings)
data/AppStore.kt single source of truth — habits/logs/settings as Compose state, persisted via multiplatform-settings
ui/UiState.kt transient UI-only state: active tab, editing/dialog state, toasts, bursts
ui/Theme.kt DotColors, DotFonts, light/dark palettes, hex()
ui/Components.kt comic-panel primitives: Panel, DButton, Chip, DField, Check, SelectField, T/H2/Meta, halftone()
ui/TodayScreen.kt ui/HabitsScreen.kt ui/InsightsScreen.kt ui/SettingsScreen.kt
App.kt header, nav switch, toast host, confirm dialog, burst overlay
platform/Backup.kt expect fun rememberBackupExporter() — per-platform actuals for export
composeApp/src/{android,desktop,ios}Main/... platform entry points + Backup actuals
composeApp/src/commonMain/composeResources/font Anton + Work Sans (OFL)
iosApp/iosApp/*.swift SwiftUI host shell (not Gradle-built; wire up via Xcode)
Requires JDK 17. Generate the wrapper once if missing: gradle wrapper.
- Desktop:
./gradlew :composeApp:run - Android:
./gradlew :composeApp:installDebug - iOS: create an Xcode project named
iosApp(or via the KMP wizard), replace its Swift files withiosApp/iosApp/*.swift, add a Run Script build phase:cd "$SRCROOT/.." && ./gradlew :composeApp:embedAndSignAppleFrameworkForXcode. - Full build:
./gradlew build. No test suite exists yet.
Everything visual funnels through ui/Components.kt and ui/Theme.kt — change those two files and every screen picks it up, since TodayScreen/HabitsScreen/InsightsScreen/SettingsScreen are all built from the same primitives (Panel, DButton, Chip, T/H2, halftone()). Don't restyle screens one-by-one; restyle the primitive.
- New colors/typography →
ui/Theme.kt(DotColors,DotFonts,hex()). Add a palette variant here, not inlineColor(...)calls in screens. - New reusable widget (e.g. a progress ring, a tag picker) → add it to
ui/Components.ktalongside the existing primitives, follow the samecomic()-border / halftone-dot visual language already there. - New screen/tab → add to the
Tabenum inui/UiState.kt, add awhen (ui.tab)branch inApp.kt, add the screen file underui/. Nav (bottom bar vs. top bar) is handled once inApp.kt, not per-screen. - New transient UI state (dialogs, selection, animation flags) →
ui/UiState.kt, not screen-localremember {}, so it survives tab switches and toasts/confirms can reach it from anywhere. - New persisted data/settings → add a field to
PersistedandAppStoreindata/AppStore.kt; every mutator must callsave()itself (there's no separate persistence layer). - New copy/microcopy → the
Copyobject indomain/Domain.kt, to keep the Pop Press voice in one place. - New platform-specific capability (share sheet, file picker, notifications, widgets) → follow the
expect/actualpattern used byplatform/Backup.ktrather than branching on platform inside common code.
<input type=date>→ plain text field expectingYYYY-MM-DD.<select>→ inline tappable option list;<datalist>→ category chips.- Dark mode follows the system theme unless overridden in Settings.