Implement HomeScreen with Vico dashboard (column chart + legend) #5

Closed
opened 2026-06-28 08:44:29 +00:00 by admin · 0 comments
Owner

Overview

Implements HomeScreen — the root dashboard. Depends on #2, #3, #4.

Prerequisites

Issues #1, #2, #3, #4 must all be merged first. This gives you:

  • All interactors available via inject()
  • All domain models
  • MainApplication already wired

What to do

1. Create ui/screens/home/HomeScreenModel.kt

Use rememberScreenModel { HomeScreenModel() } pattern. Constructor params with inject() defaults:

class HomeScreenModel(
    private val getExpenses: GetExpenses = inject(),
    private val getExpenseSummary: GetExpenseSummary = inject(),
    private val processDueRecurring: ProcessDueRecurringExpenses = inject(),
    private val exportExpensesToCsv: ExportExpensesToCsv = inject(),
    private val expensePreference: ExpensePreference = inject(),
) : ScreenModel { ... }

Note: the original spec listed getRecurringExpenses and appPreference in the constructor, but neither is used in state or actions. The default DateRangeOption is read from ExpensePreference (not AppPreference, which only carries the app theme). exportExpensesToCsv is injected to back the ExportAction in the top bar.

State:

  • expenses: StateFlow<List<ExpenseWithCategory>> — populated from getExpenses.subscribeByDateRange(currentRange) (default to DateRange.thisMonth() from expensePreference.defaultDateRange())
  • summary: StateFlow<ExpenseSummary?> — driven by dateRange.flatMapLatest { getExpenseSummary.await(it) } (null when byCategory is empty)
  • recurringBanner: StateFlow<List<Expense>?> — populated in init {} by calling processDueRecurring.await() on Dispatchers.IO; non-null list shows a dismissable Material 3 banner
  • selectedDateRange: StateFlow<DateRangeOption> — used to drive getExpenses.subscribeByDateRange

The expanded-FAB state is hoisted to HomeScreen.Content() (not part of the ScreenModel), so HomeScreenModel no longer carries isFabExpanded or a toggleFab() method.

2. Create ui/screens/home/HomeScreen.kt

  • Voyager Screen object (singleton) — current stub is empty; replace.
  • Top app bar: title "Ledgerr" + actions: Settings icon (Icons.Outlined.Settings) → navigator.push(SettingsScreen), Export icon (Icons.Outlined.IosShare) via the shared ExportAction composable.
  • Dashboard body:
    • Vico column chart of summary.byCategory (one bar per category) via CartesianChartHost(rememberCartesianChart(rememberColumnCartesianLayer(), startAxis, bottomAxis)). Per-category colors are carried by a small Row { colored swatch; category name; amount } legend below the chart (Vico 2.0.0's cartesian package only exposes Line / Column / Candlestick layers — there is no pie layer — so a column chart with a legend is the substitute, scoped to the existing Vico 2.0.0 dependency). x-axis labels are category names, y-axis labels are amounts, both via CartesianValueFormatter.
    • "Total this period" card showing summary.totalAmount formatted.
    • Period filter chips (SingleSelectFilterChipGroup over DateRangeOption.THIS_WEEK / THIS_MONTH).
    • "Manage Categories" button → navigator.push(CategoryScreen).
    • "See all" button → navigator.push(ExpenseListScreen).
    • Recent expenses list (top 5 from expenses).
  • Expanded FAB at bottom-right via the shared ui/components/ExpandedFab.kt:
    • Tap: toggle isFabExpanded.
    • Sub-actions (animated row above the FAB):
      • "Manual" → navigator.push(AddEditExpenseScreen(expenseId = null))
      • "Import Bank Statement" → navigator.push(ImportBankStatementScreen)
    • Same pattern as ExpenseListScreen Expenses tab; both screens consume the same shared ExpandedFab + MiniFab helpers from ui/components/.

3. Recurring processor

In HomeScreenModel.init {}:

screenModelScope.launch {
    val generated = withContext(Dispatchers.IO) { processDueRecurring.await() }
    _recurringBanner.value = generated.takeIf { it.isNotEmpty() }
}

4. Theme awareness

The dashboard reads appPreference.appTheme() to drive the existing AppTheme composable (already in ui/theme/). No new theme work needed in this issue.

Acceptance

  • HomeScreen renders column chart (with category legend below), total card, "Manage Categories" / "See all" buttons, recent expenses list
  • FAB expands to show Manual + Import sub-actions
  • Settings icon pushes SettingsScreen
  • Export icon uses ExportAction helper (implemented in #7)
  • Recurring processor runs on init and shows a dismissable banner when new expenses are created
  • Switching the period filter does not flash the total card to $0.00 between emissions
  • ./gradlew assembleDebug succeeds

Implementation rule

Per AGENTS.md — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.

## Overview Implements `HomeScreen` — the root dashboard. Depends on #2, #3, #4. ## Prerequisites Issues #1, #2, #3, #4 must all be merged first. This gives you: - All interactors available via `inject()` - All domain models - `MainApplication` already wired ## What to do ### 1. Create `ui/screens/home/HomeScreenModel.kt` Use `rememberScreenModel { HomeScreenModel() }` pattern. Constructor params with `inject()` defaults: ```kotlin class HomeScreenModel( private val getExpenses: GetExpenses = inject(), private val getExpenseSummary: GetExpenseSummary = inject(), private val processDueRecurring: ProcessDueRecurringExpenses = inject(), private val exportExpensesToCsv: ExportExpensesToCsv = inject(), private val expensePreference: ExpensePreference = inject(), ) : ScreenModel { ... } ``` > Note: the original spec listed `getRecurringExpenses` and `appPreference` in the constructor, but neither is used in state or actions. The default `DateRangeOption` is read from `ExpensePreference` (not `AppPreference`, which only carries the app theme). `exportExpensesToCsv` is injected to back the `ExportAction` in the top bar. State: - `expenses: StateFlow<List<ExpenseWithCategory>>` — populated from `getExpenses.subscribeByDateRange(currentRange)` (default to `DateRange.thisMonth()` from `expensePreference.defaultDateRange()`) - `summary: StateFlow<ExpenseSummary?>` — driven by `dateRange.flatMapLatest { getExpenseSummary.await(it) }` (null when `byCategory` is empty) - `recurringBanner: StateFlow<List<Expense>?>` — populated in `init {}` by calling `processDueRecurring.await()` on `Dispatchers.IO`; non-null list shows a dismissable Material 3 banner - `selectedDateRange: StateFlow<DateRangeOption>` — used to drive `getExpenses.subscribeByDateRange` The expanded-FAB state is hoisted to `HomeScreen.Content()` (not part of the ScreenModel), so `HomeScreenModel` no longer carries `isFabExpanded` or a `toggleFab()` method. ### 2. Create `ui/screens/home/HomeScreen.kt` - Voyager `Screen` object (singleton) — current stub is empty; replace. - Top app bar: title "Ledgerr" + actions: Settings icon (`Icons.Outlined.Settings`) → `navigator.push(SettingsScreen)`, Export icon (`Icons.Outlined.IosShare`) via the shared `ExportAction` composable. - Dashboard body: - **Vico column chart** of `summary.byCategory` (one bar per category) via `CartesianChartHost(rememberCartesianChart(rememberColumnCartesianLayer(), startAxis, bottomAxis))`. Per-category colors are carried by a small `Row { colored swatch; category name; amount }` legend below the chart (Vico 2.0.0's `cartesian` package only exposes `Line` / `Column` / `Candlestick` layers — there is no pie layer — so a column chart with a legend is the substitute, scoped to the existing Vico 2.0.0 dependency). x-axis labels are category names, y-axis labels are amounts, both via `CartesianValueFormatter`. - "Total this period" card showing `summary.totalAmount` formatted. - Period filter chips (`SingleSelectFilterChipGroup` over `DateRangeOption.THIS_WEEK` / `THIS_MONTH`). - "Manage Categories" button → `navigator.push(CategoryScreen)`. - "See all" button → `navigator.push(ExpenseListScreen)`. - Recent expenses list (top 5 from `expenses`). - **Expanded FAB** at bottom-right via the shared `ui/components/ExpandedFab.kt`: - Tap: toggle `isFabExpanded`. - Sub-actions (animated row above the FAB): - "Manual" → `navigator.push(AddEditExpenseScreen(expenseId = null))` - "Import Bank Statement" → `navigator.push(ImportBankStatementScreen)` - Same pattern as `ExpenseListScreen` Expenses tab; both screens consume the same shared `ExpandedFab` + `MiniFab` helpers from `ui/components/`. ### 3. Recurring processor In `HomeScreenModel.init {}`: ```kotlin screenModelScope.launch { val generated = withContext(Dispatchers.IO) { processDueRecurring.await() } _recurringBanner.value = generated.takeIf { it.isNotEmpty() } } ``` ### 4. Theme awareness The dashboard reads `appPreference.appTheme()` to drive the existing `AppTheme` composable (already in `ui/theme/`). No new theme work needed in this issue. ## Acceptance - [ ] `HomeScreen` renders column chart (with category legend below), total card, "Manage Categories" / "See all" buttons, recent expenses list - [ ] FAB expands to show Manual + Import sub-actions - [ ] Settings icon pushes `SettingsScreen` - [ ] Export icon uses `ExportAction` helper (implemented in #7) - [ ] Recurring processor runs on `init` and shows a dismissable banner when new expenses are created - [ ] Switching the period filter does not flash the total card to `$0.00` between emissions - [ ] `./gradlew assembleDebug` succeeds ## Implementation rule Per `AGENTS.md` — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.
admin changed title from Implement HomeScreen with Vico dashboard to Implement HomeScreen with Vico dashboard (column chart + legend) 2026-06-28 13:24:41 +00:00
admin closed this issue 2026-06-28 13:29:41 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/ledgerr#5