Implement full-featured Android app with backup, settings, and notifications#34
Merged
Conversation
Add ZIP full-backup system (photos + data) with inline STORE-mode ZIP…
Remove capture="environment" to allow photo library selection
Add meal timestamps, quick-select menu, heart health rating, combined…
Tighten meal step UI, fix quick-select to select-then-confirm flow
Fix quick-select chip highlighting and live text preview; restore cam…
Add A-Z sort toggle and drag-to-reorder to quick select menu
Add supplement tracker with tap-to-count and hold-to-reset
First milestone of the Option-3 full-native rewrite: a faithful, interactive Compose port of the daily logging wizard, 14-theme system, day summary, and supporting overlays. Backed by an in-memory repository (Room comes later). - 8-step wizard (breakfast→lunch→dinner→ratings→heart→flags→notes→summary) with smartStep()/defStep() start logic ported 1:1 from index.html - KetoColors theme system reproducing all 14 web themes (exact colour values) - Day navigation, swipe gestures, keto button + timestamps, summary edit jumps - Overview, supplements, quick-select, and theme-picker overlays - Gradle wrapper committed; build requires Android SDK platform 35 https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Add native Android (Jetpack Compose) interface demo
- Add key(stepIndex, viewedKey) around StepContent so Compose fully rebuilds the UI when the wizard step or date changes (fixes stale button states) - Fix ThemePanel click propagation: panel now sits above scrim as a sibling Box with pointerInput consuming all events, so swatch taps are never eaten by the close-scrim handler - Add SettingsSheet — ⚙️ button now opens a real settings screen (version, theme shortcut, data section, storage bar with future milestone notes) - Wire onSettings → Overlay.SETTINGS in WizardScreen (was routing to Overview) - Theme pick now closes the panel immediately after selection - Fix Switch composable missing height (thumb was invisible) - Remove unused TextStyle import from StepBodies - Expand Previews.kt to 20 previews covering every wizard step, every overlay, and four theme variants so all screens are visible in the Design panel - MainActivity reads vm.themeId inside setContent so Compose correctly re-wraps KetoTracker when the theme changes https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Fix 1a-1e: navigation, theme propagation, settings screen, all previews
- Fix swipe gesture: was using per-frame delta (never exceeded 40dp threshold).
Now accumulates total displacement across the gesture and fires once on
drag-end when total > 50dp, matching web app 50px threshold behaviour.
- Fix meal card styling: KetoCard now accepts compact=true which applies the
web app .card-meal values (14dp vertical / 18dp horizontal padding, 9dp gap
between children vs 24/20/18dp for regular cards). WizardScreen passes
compact=step.isMeal to KetoCard.
- Add heart GOOD auto-advance: selectHeart(GOOD) now launches a viewModelScope
coroutine that calls next() after 380ms, matching web app selHeart('good')
setTimeout behaviour. Mild/Bad selections show the notes area without
advancing.
- Add lifecycle-viewmodel-ktx dependency for viewModelScope.
https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Audit fixes: swipe gesture, meal card styling, heart auto-advance
Adds full local persistence with zero schema migration risk using a two-column Room table: `date TEXT PRIMARY KEY, data TEXT`. The `data` column stores the full DayEntry as JSON via kotlinx.serialization. Adding new fields to DayEntry only requires Kotlin changes + a default value — the SQL schema never changes, so no migrations are ever needed. Data layer: - DayEntryEntity / DayEntryDao / KetoDatabase — Room setup - IDayRepository interface with suspend load/save/deleteAll + Flow - DayRepository — Room implementation with JSON encode/decode - FakeDayRepository — in-memory impl (seeded demo data) for previews - PrefsStore — DataStore<Preferences> for theme + future settings ViewModel: - Accepts IDayRepository + optional PrefsStore via constructor injection - AppViewModel.factory(app) — production wiring (Room + DataStore) - AppViewModel.preview() — design-time factory with FakeDayRepository - allEntries: Map<String,DayEntry> state collected from observeAll() Flow - loggedKeys() / hasEntry() / entryFor() now read from in-memory map - update() saves asynchronously via viewModelScope; UI state is instant - Theme changes are persisted to DataStore immediately Build: - KSP 2.0.21-1.0.28, serialization plugin 2.0.21 added to root gradle - Room 2.6.1, DataStore 1.1.1, kotlinx-serialization-json 1.7.3 in app Removes DemoRepository (replaced by FakeDayRepository). Updates SettingsSheet to reflect that data is now truly persisted. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eference Replaces the stale "interface demo" README (which still described DemoRepository and in-memory-only persistence) with a full architecture walkthrough — package layout, AppViewModel state model, the Room JSON-column persistence design, and the Compose UI/theming approach — plus an up-to-date feature-parity table against the v1.0 PWA and a prioritized list of remaining work (calendar, snapshots, export/import, photos, etc). https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…e O(n) cost Every dao.upsert() previously re-triggered a full table re-query and re-deserialization of every stored day via observeAll()'s Flow, so each keystroke cost grew with the total number of logged days. Load the full log once at startup into an in-memory map that update()/jumpTo() now mutate directly, making per-write cost O(1) regardless of history size. Also adds: - A Channel-based one-shot error stream (AppViewModel.errors) wired to a Snackbar in WizardScreen, so failed saves/theme writes surface to the user instead of failing silently. - JUnit/coroutines-test unit tests for DateUtils and DayRepository, covering the JSON-column round-trip, forward/backward compatibility, and corrupt-row handling that the zero-migration design depends on. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
Ports the web app's IndexedDB photo store to native idioms: system camera intent (ActivityResultContracts.TakePicture + FileProvider, no CAMERA permission), on-disk JPEG storage with EXIF-correct compression (PhotoStore), and Coil-backed thumbnails/full-screen viewer/summary badges (MealPhotoArea, PhotoViewer, PhotoIndicator). Generalizes the error-toast Channel into a general-purpose message channel so photo add/remove can surface success feedback the same way. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
Ports the web app's .cal-panel to a bottom-anchored CalendarPanel overlay (styled like ThemePanel), opened from the header's date chip. DateUtils.monthGrid() generates the 42-cell Sunday-first grid with leading/trailing adjacent-month days; CalendarCell applies the same 3-tier color priority as the web version (tested+keto, >=2 keto meals, any data) plus today/viewing rings, evaluated against the in-memory allEntries map. Tapping a day in the shown month jumps straight to it (closing the long-standing "jump to an arbitrary unlogged date" gap); tapping an adjacent-month day re-centers the grid there instead. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
Native counterpart to the web app's exportAll/handleImport: SAF file pickers replace Blob-download/<input type="file">, DataPortability owns JSON encode/decode/merge, and a custom ImportConfirmDialog collapses the web app's chained confirm() calls into a single merge/overwrite/skip choice. Adds bulk-write infrastructure (saveAll/upsertAll) shared with the future Snapshot-restore feature. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
Auto-theme: PrefsStore now persists autoThemeEnabled + separate dark/ light theme choices (mirroring kt_theme_auto/kt_theme_dark_auto/ kt_theme_light_auto). MainActivity resolves the active theme via the existing resolveAutoTheme() + isSystemInDarkTheme() when enabled, and ThemePanel gains Night/Day sections plus an Auto toggle that updates each slot in place — the native counterpart of renderThemeGrid()'s auto-mode behavior. Storage stats: StorageUsage.compute() sizes the Room database file and sums every on-disk JPEG via a new PhotoStore.usage(), reporting against a fixed 512 MB display ceiling (app-private storage isn't quota-limited like browser localStorage/IndexedDB — the same choice the web app's own native code path makes). Settings now shows real used space, percentage, and a day/photo breakdown instead of a static 0% bar. Updates README feature-status table, v1↔v2 mapping, and roadmap to reflect both features moving from partial to done. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…e, and quick-select chip sync - Replace createCaptureUri/addFromUri with a CaptureTarget(file, uri) flow: PhotoStore now reads compresses directly from the temp File and always deletes it in a finally block (success, failure, or user cancellation), fixing an unbounded cacheDir/captures/ leak. Added clearStaleCaptures() swept on app startup as a safety net for process death mid-capture. - markKeto() now only stamps the current time when editing today's entry, and never overwrites an already-recorded meal time. - selectHeart() cancels its pending 380ms auto-advance job when the selection changes before it fires, so only the final pick takes effect. - QuickSelectSheet now tracks chip selection in a session-scoped Set (mirroring the web app's _qsSelected) instead of re-deriving it from comma-split free text, which mismatched typed entries and desynced on external edits. - Document why DataPortability.merge's supplements ifEmpty check is an intentional improvement over the web's literal-equality mergeEntries. - Add backup_rules.xml / data_extraction_rules.xml excluding photos/ from Auto Backup and device transfer, keeping the day-log DB backed up while skipping the larger photo cache (matches the web export's own scope).
…ename Documentation referenced the old createCaptureUri/addFromUri API that the camera-capture cache-leak fix replaced; describe the new CaptureTarget flow, temp-file cleanup (incl. cancellation and clearStaleCaptures), and the File-based compression path.
…dots - Wizard step/day content now slides + fades in from the direction of travel (Next/swipe-left from the right, Back/swipe-right from the left, day-to-day summary navigation by calendar order) via a new StepTransition wrapper. Only the entrance animates — content always reads live `vm` state, so animating an exit would flash the new step's data through the old composable. - Each overlay (ThemePanel, OverviewSheet, CalendarPanel, Supplements, QuickSelect, Settings) and the photo viewer now get their own AnimatedVisibility with a quick fade in/out, replacing the instant when-branch cut. The photo viewer renders a remembered `lastViewedPhoto` so its close fade has something to animate even after `viewingPhoto` goes null. - Dots progress indicator eases its width and color via animateDpAsState/ animateColorAsState instead of snapping between states; unified the active/inactive dot shape to a single 4dp corner radius so no shape animation is needed alongside size/color.
…eview-2oRMX Claude/android feasibility review 2o rmx
…om serializer @serializable on an enum generates a companion object that references kotlinx.serialization internal APIs (_layoutlib_/_internal_/...), which Android Studio's layoutlib preview renderer stubs with incompatible mocks. This caused a NoClassDefFoundError on Heart.<clinit> whenever any preview loaded FakeDayRepository (which accesses Heart.GOOD/MILD). The fix removes @serializable from Heart and replaces it with HeartSerializer, a hand-written KSerializer<Heart> that uses only public kotlinx.serialization APIs (PrimitiveSerialDescriptor, encodeString/decodeString). DayEntry.heart is annotated @serializable(with = HeartSerializer::class) so the JSON encoding is identical (enum name as string), but no internal APIs are referenced at class-init time. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Fix Compose Preview crash: replace @serializable Heart enum with cust…
…rrors Heart.kt and Meal.kt already existed locally but were never committed, so keeping those enums in DayEntry.kt caused 'Redeclaration' compile errors on any machine that had the local files. This commit: - Adds Heart.kt (Heart enum + HeartSerializer — the @Serializable-free serializer introduced in the previous commit to fix Compose Preview crashes) - Adds Meal.kt (Meal enum) - Removes both declarations and all their related imports from DayEntry.kt, which now only contains the DayEntry data class itself No behaviour change; same-package visibility means no import statements are needed in DayEntry.kt to reference Heart, HeartSerializer, or Meal. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Extract Heart and Meal into separate files; fix redeclaration build e…
… from DayEntry The preview renderer uses layoutlib, which stubs kotlinx.serialization with non-functional mocks. Any class whose JVM static initializer (<clinit>) references KSerializer — including @serializable data classes — crashes when first instantiated in a preview, regardless of whether serialization is ever called. Previous fixes patched Heart then DayEntry individually, but the crash kept reappearing because the root cause was untouched: @serializable on the domain model. The correct fix is to keep @serializable entirely off DayEntry. This commit: - Removes @serializable from DayEntry so its <clinit> has no serialization dependency — constructing DayEntry in FakeDayRepository is now safe in previews - Adds DayEntrySurrogate: an internal @serializable mirror of DayEntry used only by DayRepository and DataPortability (neither of which are ever loaded during preview initialization) - Updates DayRepository and DataPortability to encode/decode via the surrogate - The JSON format on disk is identical (same field names, same types, same defaults), so existing database records and exports are fully compatible https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Fix root cause of Compose Preview render crash: isolate serialization…
Three quality-of-life improvements to reduce required taps: 1. Ratings auto-advance pickRating() now schedules a 380ms auto-advance once all three ratings (energy, happiness, portion) are filled — matching the web app's per-step selRate() behaviour, collapsed onto the single combined Ratings step. Re-picking any rating cancels and resets the timer. All manual navigation (next/back/skip/editAt/jumpTo) calls cancelPendingAdvance() so the delayed fire can never mis-step if the user navigates first. 2. Lunch gate before dinner defStep() now uses an effective smart step: if the clock says dinner time (≥14h) but lunch is still empty, it lands on LUNCH instead of DINNER. Lunch must be explicitly filled (or skipped with the Skip button) before the time-based jump to dinner is allowed. 3. Fasted-breakfast auto-mark loadDateEntry() (called at startup and on jumpTo) silently sets breakfastKeto=true for today when it is past 10:00 and breakfast text is blank and the flag is not yet set — recording a deliberate fast, reducing one extra tap for intermittent fasters. isIncomplete(BREAKFAST) treats a keto-flagged empty breakfast as complete, so defStep() skips it cleanly and lands on the next needed step. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
DayEntryDao.upsertAll was added for bulk saves but InMemoryDao (the in-memory test double) was never updated to implement it, causing a 'not abstract and does not implement abstract member upsertAll' compile error in compileDebugUnitTestKotlin. https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX Claude/android feasibility review 2o rmx
…eriodic backup Implements web-app feature parity for the Settings screen: Data model / persistence: - SnapshotMeta (@serializable) — lightweight metadata stored in DataStore; full entry data stored as filesDir/snapshots/snap_{id}.json via SnapshotStore - ZipPortability — bundles data.json + every JPEG into a .zip for full backup - BackupWorker (CoroutineWorker, WorkManager) — periodic JSON backup to getExternalFilesDir("backups"), prunes to last 7 files (daily/weekly schedule) - PrefsStore expanded with snapshots, quickSelectItems, backupEnabled, backupFrequency flows backed by DataStore - PhotoStore.listAllPhotoFiles() + restorePhoto() for ZIP export/import AppViewModel additions: - Snapshots: saveSnapshot, restoreSnapshot, deleteSnapshot, exportSnapshot (up to 25, mirrors web app limit; restoreSnapshot resets to today after restore) - Quick-select: addQuickSelectItem, removeQuickSelectItem, resetQuickSelectDefaults (persisted in DataStore, falls back to DEFAULT_QUICK_FOODS constant) - Backup scheduling: setBackupEnabled(context), setBackupFrequency(context) - ZIP export/import: exportFullBackup, importFromZip (reuses pendingImport flow; photos are restored after entries commit inside confirmImport) - cancelImport now clears pendingZipPhotos Settings UI (full restructure): - Category-based navigation with Crossfade(200ms) animation between pages - SAF launchers at top level so results survive sub-page transitions - Pages: Main (nav list) · Data & Backups · Quick-Select Foods · Notifications (placeholder) · Storage · Appearance · About - Data & Backups: JSON export/import, ZIP backup/import, named snapshot list with restore/export/delete confirmation dialogs, periodic backup toggle with Daily/Weekly frequency chips - Quick-Select Foods: removable chip list + single-line BasicTextField to add items + "Restore Defaults" button - Sheets.kt QuickSelectSheet now reads vm.quickSelectItems (dynamic, persisted) instead of the old hard-coded QUICK_FOODS constant https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX feat: full Settings overhaul — snapshots, ZIP backup, quick-select, p…
- NotificationHelper: rich daily reminder with BigTextStyle, lock-screen
visibility (VISIBILITY_PUBLIC), gold accent colour, avocado small icon,
launcher large icon, and "Log Now" action button
- ic_notification.xml: monochrome avocado vector (evenOdd fill creates
transparent pit hole so Android's tinting works correctly)
- ReminderWorker: CoroutineWorker scheduled via WorkManager that fires at
the user-chosen hour; smart suppression skips notification when all three
meals are already logged for the day
- POST_NOTIFICATIONS permission added to AndroidManifest (API 33+ runtime)
- NotificationHelper.createChannel() called in MainActivity.onCreate() so
the channel is registered before any worker can fire
- PrefsStore: notificationsEnabled + notificationHour (default 20 / 8 PM)
flows backed by DataStore
- AppViewModel: setNotificationsEnabled / setNotificationHour methods that
create the channel, schedule or cancel ReminderWorker, and persist prefs
- SettingsSheet rewrite:
- Merged "Storage" and "Data & Backups" into single "Data & Storage" tab;
StorageBar sits at the top per UX request, backup/snapshot/export
sections follow below
- Fully functional Notifications page: runtime permission handling for
API 33+, toggle pill, morning/afternoon/evening time presets, and an
in-app preview card showing exactly what the notification will look like
https://claude.ai/code/session_01JkcDPXGfphLVjv2HReg96W
…eview-2oRMX feat: full notification system + merge Storage/Data&Backups tabs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.