58 Commits

Author SHA1 Message Date
admin a5a1422c47 Merge pull request 'fix(#41,#42,#43): numeric amount input, clickable DateField, search-based category picker' (#47) from fix/41-42-43-form-input-fixes into main
Reviewed-on: #47
2026-06-28 14:59:29 +00:00
admin f6ac2aace1 Merge pull request 'fix(#44): pin expense filter chips directly under tabs' (#48) from fix/44-expenses-screen-empty-gap-tabs-chips into main
Reviewed-on: #48
2026-06-28 14:58:58 +00:00
Achmad Setyabudi Susilo 0977e34a2d fix(#44): pin expense filter chips directly under tabs
Hoist the date-range filter chip group and divider out of the
LazyColumn and into a Column wrapper, so the chips sit directly
under the Expenses/Recurring tabs and the list fills the rest of
the page. Previously the chips were the first item of the
LazyColumn, which left a large empty area between the tabs and
the chips.

Also add Modifier.fillMaxSize() to the Recurring tab's LazyColumn
so it fills the pager page (no chips to hoist, but the layout
is now consistent with the Expenses tab).
2026-06-28 21:57:16 +07:00
Achmad Setyabudi Susilo b7c9c39862 fix(#41,#42,#43): numeric amount input, clickable DateField, search-based category picker
#41: pipe amount input through sanitizeAmountInput() (digits + single
decimal) in both AddEditExpenseScreenModel and AddEditRecurringScreenModel
so KeyboardType.Decimal is no longer bypassable via paste or hardware keys.

#42: wrap DateField's OutlinedTextField in a Box.clickable so the whole
row (label, text, icon) opens the date picker, not just the trailing icon.

#43: replace CategoryDropdownField with a new CategoryPickerField that
opens a generic ListSearchDialog (search bar, scrollable list, swatch,
checkmark on selected row), decoupled from the Preference machinery.
ListSearchPreferenceWidget is unchanged.
2026-06-28 21:55:33 +07:00
admin 7dc95a8191 Merge pull request 'fix(#40): tint status and navigation bars to match active theme and flip icon appearance' (#46) from feat/40-status-and-navigation-bar-colors-dont-follow-current-theme into main
Reviewed-on: #46
2026-06-28 14:54:58 +00:00
Achmad Setyabudi Susilo ec941e9e04 fix(#40): tint status and navigation bars to match active theme and flip icon appearance 2026-06-28 21:53:34 +07:00
admin 863a283239 Merge pull request 'fix(#39): shrink empty-state illustration from 120dp to 96dp' (#45) from feat/39-empty-state-illustration-too-large into main
Reviewed-on: #45
2026-06-28 14:53:11 +00:00
Achmad Setyabudi Susilo 8b16d72e88 fix(#39): shrink empty-state illustration from 120dp to 96dp 2026-06-28 21:52:06 +07:00
admin 7963ac55e2 Merge pull request 'fix(#25,#26,#28): Expenses screen polish — scrim, tab badges, shared SearchToolbar' (#37) from fix/25-26-28-expenses-screen-polish into main
Reviewed-on: #37
2026-06-28 14:33:43 +00:00
admin d7d49fe6a9 Merge pull request 'fix(#23,#24,#27): theme preference wiring, dd MMM yyyy dates, calendar icon in DateField' (#38) from fix/23-24-27-ui-polish into main
Reviewed-on: #38
2026-06-28 14:32:56 +00:00
Achmad Setyabudi Susilo d5c0d395c5 Merge origin/main into fix/25-26-28-expenses-screen-polish
Main moved ahead with the home-polish (#29/#32/#33), category HSV color
picker (#30), and category lock-icon fix (#31) PRs. The only conflicts
were in ExpenseListScreen.kt:

- Imports: main added AppBar + EmptyStateIllustration. The branch had
  already removed AppBar (replaced by SearchToolbar) and replaced the
  inline text empty state. Resolution: keep the branch's
  AppBarTitle/SearchToolbar/TabText set, drop AppBar (no longer used),
  add EmptyStateIllustration.
- ExpensesTabContent empty state: main replaced the Box+Text
  'No expenses yet' with EmptyStateIllustration. Resolution: use
  EmptyStateIllustration (consistent with main's new design) but keep
  the branch's three-way message selection (no results vs no data).

For visual consistency between the two tabs, also swap
RecurringTabContent's empty state to EmptyStateIllustration. No
behavioral change for the recurring case — same per-tab vs no-results
message selection, same strings, just rendered through the shared
component.

All other files (build.gradle.kts, libs.versions.toml, HomeScreen,
SettingsScreen, CategoryScreen, EmptyStateIllustration.kt, strings.xml)
auto-merged cleanly.
2026-06-28 21:32:23 +07:00
Achmad Setyabudi Susilo 2f8368d4ef fix(#23): wire AppTheme preference in MainActivity 2026-06-28 21:30:54 +07:00
Achmad Setyabudi Susilo dca79df056 fix(#24): format user-visible dates as dd MMM yyyy 2026-06-28 21:30:32 +07:00
Achmad Setyabudi Susilo 903cd3a70d fix(#27): replace "Pick" TextButton with trailing calendar icon in DateField 2026-06-28 21:30:10 +07:00
admin 5f6de30958 Merge pull request 'feat(#29,#32,#33): home polish — Manage Categories to Settings, View All, empty-state illustration' (#36) from feat/home-polish-29-32-33 into main
Reviewed-on: #36
2026-06-28 14:25:57 +00:00
Achmad Setyabudi Susilo 16236c6d6c fix(#26): replace inline search with SearchToolbar (debounced, shared)
Replace the inline OutlinedTextField inside ExpensesTabContent with
the shared SearchToolbar in the topBar. The query is shared across
both tabs; closing the toolbar (X / navigate-up) deactivates search
and clears the filter.

In ExpenseListScreenModel:
- searchQuery becomes StateFlow<String?> (null = inactive, "" = active
  empty, "foo" = active query)
- setSearchQuery now accepts String? to match SearchToolbar's
  onChangeSearchQuery signature
- expenses and recurring combine on
  _searchQuery.debounce(SEARCH_DEBOUNCE_MILLIS).distinctUntilChanged()
  so fast typing does not re-filter on every keystroke
- recurring is now filtered by query against category name and
  recurring note (case-insensitive substring)

In ExpenseListScreen:
- Remove the inline search field, the date-range-filter search hint
  label, and the now-unused OutlinedTextField / Icons.Outlined.Search
  imports
- Both ExpensesTabContent and RecurringTabContent now take a nullable
  searchQuery and show a generic 'No results' message when the active
  filter empties the list, or the per-tab empty copy otherwise
- Add the expense_list_no_results string
2026-06-28 21:23:13 +07:00
Achmad Setyabudi Susilo f0803431f9 feat(#29,#32,#33): home polish — move Manage Categories to Settings, View All button, empty-state illustration 2026-06-28 21:22:32 +07:00
Achmad Setyabudi Susilo 5953111897 fix(#25): render expense tab counts via TabText
Replace the plain Text inside PrimaryTabRow's Tab with the shared
TabText composable. Pass the current size of the expenses and
recurring state flows as badgeCount so each tab shows a small pill
with the (post-filter) list size. Counts update reactively as
items are added, deleted, or filtered by search/date.
2026-06-28 21:22:09 +07:00
admin 38baeef401 Merge pull request 'feat(#30): replace category swatch grid with HSV color picker' (#35) from feat/30-category-rgb-color-picker into main
Reviewed-on: #35
2026-06-28 14:21:44 +00:00
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 de4d411a58 Merge pull request 'fix(#31): vertical-align lock icon with trash icon in category rows' (#34) from fix/31-vertical-align-lock-icon into main
Reviewed-on: #34
2026-06-28 14:21:12 +00:00
Achmad Setyabudi Susilo b39ca61cfb feat(#30): replace category swatch grid with HSV color picker 2026-06-28 21:17:21 +07:00
Achmad Setyabudi Susilo 86bea46c30 fix(#31): vertical-align lock icon with trash icon in category rows 2026-06-28 21:09:33 +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
admin 006d3693ab Merge pull request 'Implement HomeScreen with Vico dashboard (#5)' (#20) from feat/5-implement-homescreen-with-vico-dashboard into main
Reviewed-on: #20
2026-06-28 13:29:41 +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
admin a3c0976edd Merge pull request 'chore(#9): enable Room exportSchema and configure schemaLocation' (#19) from chore/9-enable-room-exportschema into main
Reviewed-on: #19
2026-06-28 12:55:34 +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
admin 6f7d24a303 Merge pull request 'Implement CategoryScreen, ImportBankStatementScreen, SettingsScreen, and ExportAction helper (#7)' (#17) from feat/7-implement-screens-export into main
Reviewed-on: #17
2026-06-28 12:06:47 +00: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 1a71c4c9e6 chore(agents): require reviewer to use COMMENT state (same-account Gitea MCP)
Gitea blocks self-approval and self-requested-changes, so the reviewer
agent (which always reviews its own work on this same-account setup)
must always submit with state: COMMENT. Verdicts go in the summary body;
blocking concerns are marked inline with **Blocking:** / **Nit:** /
**Suggestion:** prefixes so the implementor can triage them.
2026-06-28 18:32:47 +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 567f6a7cee chore(agents): document local.properties resolution for worktrees 2026-06-28 17:53:17 +07:00
admin 36deb46a28 Merge pull request 'Implement bankstatement, export, and data interactors (#4)' (#16) from feat/4-implement-bankstatement-export-data into main
Reviewed-on: #16
2026-06-28 10:42:01 +00:00
Achmad Setyabudi Susilo 94d40d4216 feat(#4): implement bankstatement, export, and data interactors 2026-06-28 17:37:04 +07:00
Achmad Setyabudi Susilo 105c858d57 chore(agents): split AGENTS.md into shared + per-role agents
Move architecture, package structure, dependencies, and implementation
rules into .opencode/agent/implementor.md (default primary agent). Add
.opencode/agent/reviewer.md (read-only primary agent) with PR review
workflow that leaves inline review comments only -- no issue creation,
no edits, no commits. AGENTS.md is now the shared context both agents
load: workflow, git/PR conventions, and docs index. Set
default_agent: implementor in opencode.json.
2026-06-28 17:06:06 +07:00
admin 1bb6747610 Merge pull request 'Implement expense interactors (#2)' (#11) from feat/2-implement-expense-interactors into main
Reviewed-on: #11
2026-06-28 09:58:45 +00: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
admin ec21462d03 Merge pull request 'Implement recurring interactors (#3)' (#10) from feat/3-implement-recurring-interactors into main
Reviewed-on: #10
2026-06-28 09:56:38 +00: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