Commit Graph

24 Commits

Author SHA1 Message Date
Achmad Setyabudi Susilo b698f5084f fix(#28): add scrim when ExpandedFab is open
Add ExpandedFabScrim composable that renders a Material 3 scrim
overlay fading in/out in sync with the mini-FABs. Tapping the scrim
dismisses the FAB.

Move the ExpandedFab out of Scaffold's floatingActionButton slot and
into the body inside a Box, so the scrim can match the body size via
matchParentSize() and stack above the list but below the FAB. Add a
BackHandler that dismisses the FAB on system back while it is open.
2026-06-28 21:21:36 +07:00
admin ba99eac4be Merge pull request 'fix(#21): move DB I/O to Dispatchers.IO in AddEdit screen models' (#22) from feat/21-move-db-io-to-dispatchers-io into main
Reviewed-on: #22
2026-06-28 13:30:20 +00:00
Achmad Setyabudi Susilo 0bf47d5c94 fix(#21): move DB I/O to Dispatchers.IO in AddEdit screen models
Wrap getExpenses.awaitOne, upsertExpense.await, getRecurringExpenses.awaitOne
and upsertRecurringExpense.await in withContext(Dispatchers.IO) { ... } so
the suspending Room calls run off the main thread. State-flow updates stay
inside screenModelScope.launch, which is Main-bound, and execute after the
withContext block returns.
2026-06-28 20:28:46 +07:00
Achmad Setyabudi Susilo 3ddfaa0a22 fix(#5): address PR review — share ExpandedFab, inject GetExpenseSummary, column-chart spec
- Add ui/components/ExpandedFab.kt with ExpandedFab + MiniFab helpers; HomeScreen and ExpenseListScreen both consume it, the tab-collapsing LaunchedEffect in ExpenseListScreen is hoisted to its Content()
- Inject GetExpenseSummary in HomeScreenModel; drive summary via dateRange.flatMapLatest { getExpenseSummary.await(it) } (fixes the period-filter total-card flicker) and drop the inline combine(expenses, dateRange) recomputation
- Hoist isFabExpanded out of HomeScreenModel into HomeScreen.Content() so the FAB state is local to the composable
- Convert HomeScreenModel.exportToCsv from a callback to a suspend fun returning Result<Unit>; the screen does the snackbar dispatch on the coroutineScope
- Consolidate DateRangeOption label mapping to a single DateRangeOption.labelRes() / .labelText() pair (one source of truth)
- Rename FAB string keys to shared fab_manual / fab_import and drop the home_fab_* duplicates
- Update docs/04-implementation-plan.md and .opencode/agent/implementor.md Charts sections to reflect the Vico 2.0.0 column-chart-with-legend substitution (Vico 2.0.0 has no pie layer)
2026-06-28 20:25:53 +07:00
Achmad Setyabudi Susilo a0ccf22e67 feat(#5): implement HomeScreen with Vico dashboard
- Add HomeScreenModel with expenses/summary/recurring-banner/fab state flows and a getExpenses + processDueRecurring + exportExpensesToCsv + expensePreference constructor
- Replace the HomeScreen stub with a Material 3 dashboard: AppBar (Export + Settings), total card, period filter, Vico ColumnCartesianLayer chart with per-category legend, manage-categories/see-all actions, recent expenses, and an expanded FAB exposing Manual + Import sub-actions
- Add home strings and a home_recurring_banner plurals resource
2026-06-28 20:11:01 +07:00
admin 8ce0dcc678 Merge pull request 'Implement ExpenseListScreen, AddEditExpenseScreen, AddEditRecurringScreen (#6)' (#18) from feat/6-implement-expense-list-add-edit-screens into main
Reviewed-on: #18
2026-06-28 12:57:17 +00:00
Achmad Setyabudi Susilo ed8b3577dc chore(#9): enable Room exportSchema and configure schemaLocation 2026-06-28 19:51:33 +07:00
Achmad Setyabudi Susilo e8c7a14c75 fix(#6): address PR review
- matchesQuery: use %.2f format to match the display (was Double.toString, which can use scientific notation and disagree with the row's %.2f)
- Remove unused Switch import in AddEditRecurringScreen (the active toggle uses ToggleItem)
- Extract CategoryDropdownField to ui/components/CategoryDropdownField.kt so both add/edit screens share one implementation; takes label as a parameter
- Remove unused recurring_list_active string
2026-06-28 19:49:02 +07:00
Achmad Setyabudi Susilo b0a62bedf0 feat(#6): implement ExpenseListScreen, AddEditExpenseScreen, AddEditRecurringScreen 2026-06-28 19:36:20 +07:00
Achmad Setyabudi Susilo 22863bfcd6 fix(#7): emit snackbar as resource id, resolve with stringResource in UI
Drop the Context injection from the ScreenModel. Emit a sealed
ImportSnackbarMessage carrying either a @StringRes id or a dynamic
text (for the error.message case where the bank importer surfaces a
useful 'BRI import not yet implemented' string). The screen resolves
resource ids via stringResource() in Composable scope and shows the
snackbar; the original error.message info is preserved via the
Dynamic variant instead of being dropped.
2026-06-28 18:46:17 +07:00
Achmad Setyabudi Susilo 5893ffc955 fix(#7): localize import snackbar strings and drop class.simpleName fallback
The three snackbar strings in ImportBankStatementScreenModel were
hardcoded English while the rest of the app uses stringResource. Move
them to res/values/strings.xml. Also drop the
error::class.simpleName fallback in the failure branch — it surfaced
Kotlin class names like NotImplementedError to end users. Now falls
back to the localized 'Import failed' string.
2026-06-28 18:32:58 +07:00
Achmad Setyabudi Susilo ce01c175df fix(#7): use UTC for DateField initial seed to match pick conversion
DateField seeded the picker with ZoneId.systemDefault() but converted
the selectedDateMillis back with ZoneId.of("UTC"). For any non-UTC
user, the field and the picker displayed different days, and
confirming without re-picking silently shifted the export range by a
day. Use UTC on both sides (the DatePickerState contract is that
selectedDateMillis is UTC midnight of the picked day).
2026-06-28 18:27:49 +07:00
Achmad Setyabudi Susilo 7782df8b36 fix(#7): move DB/IO off main thread in ScreenModels
screenModelScope is backed by PlatformMainDispatcher (Main.immediate),
so direct interactor calls run DB queries and file I/O on the UI
thread. Switch reactive flows with .flowOn(Dispatchers.IO) and wrap
suspend calls in withContext(Dispatchers.IO).
2026-06-28 18:10:35 +07:00
Achmad Setyabudi Susilo f6860544e4 feat(#7): implement CategoryScreen, ImportBankStatementScreen, SettingsScreen, and ExportAction helper 2026-06-28 17:55:22 +07:00
Achmad Setyabudi Susilo 94d40d4216 feat(#4): implement bankstatement, export, and data interactors 2026-06-28 17:37:04 +07:00
Achmad Setyabudi Susilo ca2992fceb Merge branch 'main' of https://git.achmad.dev/admin/ledgerr into feat/2-implement-expense-interactors
# Conflicts:
#	app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt
2026-06-28 16:57:58 +07:00
Achmad Setyabudi Susilo 63bfe2a6b5 fix(#2): address PR review — use row mapper, require id==0, drop ignoreCase on amount, doc drift, orphan-category note 2026-06-28 16:52:42 +07:00
Achmad Setyabudi Susilo 7bb65025a2 fix(#3): make ProcessDueRecurringExpenses atomic via withTransaction
Address review: insert + advance pair must run in one DB transaction to
prevent duplicate expenses if the process is killed mid-loop.
2026-06-28 16:52:07 +07:00
Achmad Setyabudi Susilo 6a11284212 feat(#3): implement recurring interactors 2026-06-28 16:33:41 +07:00
Achmad Setyabudi Susilo 46f882b3c3 feat(#2): implement expense interactors 2026-06-28 16:33:39 +07:00
Achmad Setyabudi Susilo 547343992a fix(#1): address PR review — split upsert into insert/update, add @Transaction, runBlocking seed, trailing newline
Per review on PR #8 (#8):

- Split @Upsert into @Insert(OnConflictStrategy.REPLACE) + @Update in all 3 DAOs.
  @Upsert returns -1 on the update path, so callers wanting the row ID would
  get a junk value. Interactors now call insert vs update based on id == 0.
  UpsertCategory returns category.id explicitly for the id != 0 branch.
- Add @Transaction to the 3 @Relation queries (ExpenseDao.subscribeAll,
  ExpenseDao.subscribeByDateRange, RecurringExpenseDao.subscribeAll). This
  silences the KSP warnings the PR body mentioned and makes the intent
  explicit.
- Switch MainApplication seeding from a fire-and-forget CoroutineScope to
  runBlocking(Dispatchers.IO). A fast first-tap on HomeScreen could otherwise
  call GetCategories.awaitDefault() before seeding completed and crash.
- Add documenting comment on CategoryDao.getDefault() noting that the
  'only one isDefault = 1' invariant is maintained at the interactor layer
  (partial unique index would be a v2 migration).
- Add trailing newline to app/build.gradle.kts.
- File follow-up issue #9 for flipping exportSchema to true before any v2
  migration lands.
2026-06-28 16:09:15 +07:00
Achmad Setyabudi Susilo 8e7a6cfe3f feat(#1): implement category feature and wire DI foundation
- Add Room 2.7.1, PDFBox-Android 2.0.27.0, Vico 2.0.0 dependencies
- Bump compileSdk 36 -> 37 (transitive deps require it)
- Add LocalDateConverter + 3 entities + 3 DAOs with @Relation rows + mappers + AppDatabase v1
- Add all domain models (expense, category, recurring, bankstatement, preference)
- Implement 4 category interactors: GetCategories, UpsertCategory, DeleteCategory, SeedDefaultCategories
- Add minimal ReassignExpenseCategory (full expense interactors come in #2)
- Wire CoreModule (SharedPreferences + AndroidPreferenceStore), DataModule (Room + 3 DAOs), PreferenceModule, DomainModule (category factories)
- Create MainApplication with Koin start and SeedDefaultCategories trigger
- Register MainApplication in AndroidManifest
- Add missing string resources for pre-existing copied UI components (confirm, cancel, ok, general_selected, general_not_selected, disabled)
2026-06-28 15:53:58 +07:00
Achmad Setyabudi Susilo 3e30423083 docs: add architecture docs, AGENTS.md, CLAUDE.md, and copy UI components
- Add docs/01-04 covering data model, interfaces, function TODOs, and implementation plan
- Add AGENTS.md with project conventions, architecture rules, feature workflow, and git policy
- Add CLAUDE.md pointing to AGENTS.md
- Copy UI components and utils from info-krl-android (preference widgets, scrollbars, etc.)
- Delete obsolete UiModule.kt
2026-06-28 15:08:37 +07:00
Achmad Setyabudi Susilo dfca375a9b chore: setup project 2026-06-28 13:21:57 +07:00