Skip to content

feat: chart for income expense#2052

Open
Nagarjuna0033 wants to merge 11 commits into
openMF:devfrom
Nagarjuna0033:feat/charts-income-expense
Open

feat: chart for income expense#2052
Nagarjuna0033 wants to merge 11 commits into
openMF:devfrom
Nagarjuna0033:feat/charts-income-expense

Conversation

@Nagarjuna0033

@Nagarjuna0033 Nagarjuna0033 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Issue Fix

Jira Task: 421

Screenshots

image

Summary by CodeRabbit

  • New Features

    • Added a home screen Finance widget showing balance, budget status, and quick actions (sign in / open dashboard, add income/expense).
    • Enabled widget-driven deep links (mifospay://) for dashboard and add actions, with navigation integrated into the app flow.
    • Implemented background widget synchronization with scheduled refresh and on-demand updates after key transfers/login events.
    • Enhanced the History screen with a credits vs. debits pie chart.
  • Bug Fixes

    • Improved deep-link handling during activity relaunch to prevent repeated triggers.
  • Chores

    • Updated Android runtime libraries and UI/chart dependencies.

@Nagarjuna0033
Nagarjuna0033 requested a review from a team June 23, 2026 19:15
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an Android Glance home-screen finance widget with WorkManager-based periodic refresh and deep-link actions, a multiplatform deep-link buffering layer that stores URIs across the auth gate and navigates after login, widget state refresh hooks in transfer and login flows, and a Vico-based income/expense pie chart in the history screen.

Changes

Finance Widget and Deep Link Infrastructure

Layer / File(s) Summary
Widget data model and service contracts
core/model/.../widget/WidgetData.kt, cmp-shared/.../widget/WidgetState.kt, cmp-shared/.../widget/WidgetDataProvider.kt, core/data/.../repository/WidgetRepository.kt, core/data/.../repository/WidgetManagerRepository.kt, core/data/.../util/WidgetSyncService.kt
Introduces WidgetData serializable model with budget computed properties, WidgetState sealed interface (Unauthenticated/Authenticated/Error), WidgetDataProvider flow interface, WidgetRepository, WidgetManagerRepository, and WidgetSyncService contracts.
Widget data persistence and flow implementations
core/datastore/.../WidgetPreferenceDataSource.kt, cmp-shared/.../widget/WidgetDataProviderImpl.kt, core/data/.../repositoryImpl/WidgetRepositoryImpl.kt, core/data/.../repositoryImpl/WidgetManagerRepositoryImpl.kt, core/datastore/.../UserPreferencesDataSource.kt
WidgetPreferenceDataSource persists WidgetData via Settings with a StateFlow. WidgetDataProviderImpl combines auth preferences with a refresh trigger to emit WidgetState. WidgetRepositoryImpl enriches stored data with the default account's balance from AccountRepository. WidgetManagerRepositoryImpl orchestrates repository and sync service. clearInfo() is extended to reset all in-memory flow state.
DI wiring for widget stack
core/datastore/.../di/PreferenceModule.kt, core/data/.../di/RepositoryModule.kt, cmp-shared/.../di/KoinModules.kt, core/domain/.../LoginUseCase.kt, core/domain/.../di/DomainModule.kt
Registers WidgetPreferenceDataSource, WidgetRepository, WidgetManagerRepository, and WidgetDataProviderImpl in their respective Koin modules. Wires WidgetManagerRepository into LoginUseCase to trigger a widget refresh after login.
Multiplatform deep-link buffering infrastructure
cmp-shared/src/commonMain/.../navigation/PendingDeepLinkStore.kt, cmp-shared/src/androidMain/.../navigation/PendingDeepLinkStore.android.kt, cmp-shared/src/nonAndroidMain/.../navigation/PendingDeepLinkStore.nonAndroid.kt, cmp-shared/src/commonMain/.../navigation/ConsumePendingDeepLink.kt, cmp-shared/src/androidMain/.../navigation/ConsumePendingDeepLink.android.kt, cmp-shared/src/nonAndroidMain/.../navigation/ConsumePendingDeepLink.nonAndroid.kt, cmp-shared/src/commonMain/kotlin/.../MifosPayApp.kt, cmp-shared/src/androidMain/kotlin/.../MifosPayApp.android.kt, cmp-shared/src/nonAndroidMain/kotlin/.../MifosPayApp.nonAndroid.kt, cmp-shared/src/commonMain/.../navigation/RootNavGraph.kt
PendingDeepLinkStore (expect/actual) buffers a deep-link URI string across the auth gate. HandleDeepLinks (Android) reads incoming activity intents and stores the URI; non-Android is a no-op. consumePendingDeepLink (Android) reconstructs an ACTION_VIEW intent and dispatches it to the nav controller; called from RootNavGraph when MAIN_GRAPH is entered.
Android Glance widget UI, receiver, and deep-link actions
cmp-android/.../widget/WidgetDeepLink.kt, cmp-android/.../widget/ui/WidgetAction.kt, cmp-android/.../widget/ui/Actions.kt, cmp-android/.../widget/FinanceGlanceWidget.kt, cmp-android/.../widget/FinanceWidgetReceiver.kt, cmp-android/src/main/res/xml/finance_widget_info.xml, cmp-android/src/main/res/values/strings.xml, feature/home/.../navigation/HomeNavigation.kt
WidgetDeepLink and WidgetAction define URI constants and the enum mapping. Three ActionCallback implementations launch deep-link intents. FinanceGlanceWidget renders FullWidget/CompactWidget/UnauthenticatedWidget/ErrorWidget based on WidgetState and width. FinanceWidgetReceiver controls schedule/cancel. Widget provider XML and string resource added. Home screen route registers the mifospay://dashboard deep link.
App bootstrap and WorkManager integration
cmp-android/.../MifosPayApp.kt, cmp-android/.../MainActivity.kt, cmp-android/src/main/AndroidManifest.xml, cmp-android/.../widget/AndroidWidgetModule.kt, cmp-android/.../widget/AndroidWidgetSyncService.kt, cmp-android/.../widget/WidgetRefreshWorker.kt, cmp-android/build.gradle.kts
MifosPayApp implements Configuration.Provider, initializes Koin with workManagerFactory and androidWidgetModule, then schedules widget updates via widgetManager.schedule() and enqueues WidgetRefreshWorker. AndroidWidgetSyncService calls Glance update APIs. WidgetRefreshWorker runs on a 15-minute periodic schedule with network constraints and exponential backoff. Manifest adds deep-link filter for MainActivity, replaces FileProvider with InitializationProvider removing the default WorkManagerInitializer, and registers FinanceWidgetReceiver.
Widget refresh on transfer and logout
feature/make-transfer/.../MakeTransferViewModel.kt, feature/transfer-interbank/.../InterbankTransferViewModel.kt, feature/transfer-intrabank/.../TransferConfirmViewModel.kt, cmp-shared/.../MifosPayViewModel.kt
MakeTransferViewModel, InterbankTransferViewModel, and TransferConfirmViewModel inject WidgetManagerRepository and call refresh() after successful transfers. MifosPayViewModel calls clear() during logout.
Dependency updates and iOS minor fix
cmp-android/dependencies/prodReleaseRuntimeClasspath.*, cmp-android/build.gradle.kts, gradle/libs.versions.toml, cmp-shared/src/iosMain/kotlin/MifosViewController.kt
Dependency tree and flat classpath files updated to include Glance, WorkManager, Room, and koin-androidx-workmanager artifacts. Vico chart library added to libs.versions.toml. iOS NSUserDefaults.setObject call reformatted to multi-line with no logic change.

Income/Expense Pie Chart in History

Layer / File(s) Summary
Vico pie chart component across platforms
gradle/libs.versions.toml, core/ui/build.gradle.kts, core/ui/src/commonMain/.../MifosIncomeExpenseVisual.kt, core/ui/src/androidMain/..., core/ui/src/desktopMain/..., core/ui/src/jsMain/..., core/ui/src/wasmJsMain/..., core/ui/src/nativeMain/...
Adds Vico version and library entries to dependency catalog. Defines expect MifosPieIncomeExpense and shared MifosLegendItem in commonMain. Provides identical Vico PieChartHost + PieChartModelProducer + legend implementations for Android, desktop, JS, and wasmJS; native is a no-op stub.
History screen integration
feature/history/.../HistoryViewModel.kt, feature/history/.../HistoryScreen.kt
HistoryViewModel gains calculateSummary() computing totalCredit/totalDebit from loaded transactions. HistoryState gains those two nullable fields. HistoryScreen inserts MifosPieIncomeExpense above the transaction list in a spaced Column.

Sequence Diagram(s)

sequenceDiagram
  participant Widget as FinanceGlanceWidget
  participant WDP as WidgetDataProviderImpl
  participant WRI as WidgetRepositoryImpl
  participant WPDS as WidgetPreferenceDataSource
  participant AccRepo as AccountRepository

  Widget->>WDP: collect widgetStateFlow
  WDP->>WRI: getWidgetData()
  WRI->>WPDS: read stored WidgetData
  WRI->>AccRepo: getSelfAccounts()
  AccRepo-->>WRI: accounts list
  WRI-->>WDP: WidgetData(balance, currency, accountNumber)
  WDP-->>Widget: WidgetState.Authenticated
  Widget->>Widget: render FullWidget or CompactWidget
Loading
sequenceDiagram
  participant Android as AndroidSystem
  participant MA as MainActivity
  participant HL as HandleDeepLinks
  participant PDS as PendingDeepLinkStore
  participant RNG as RootNavGraph
  participant CPD as consumePendingDeepLink
  participant Nav as NavHostController

  Android->>MA: onNewIntent(mifospay://add-income)
  MA->>MA: setIntent(intent)
  HL->>HL: LaunchedEffect(activity.intent)
  HL->>PDS: store("mifospay://add-income")
  HL->>MA: intent.data = null
  Note over RNG: Auth cleared → MAIN_GRAPH entered
  RNG->>CPD: LaunchedEffect(Unit)
  CPD->>PDS: consume()
  PDS-->>CPD: "mifospay://add-income"
  CPD->>Nav: handleDeepLink(ACTION_VIEW intent)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • revanthkumarJ
  • niyajali

Poem

🐰 A widget hops onto your home screen today,
With balances, budgets, and quick actions to play!
Deep links now travel through auth gates with care,
A pie chart of credits and debits laid bare.
WorkManager ticks every quarter-hour anew —
This rabbit built widgets for people like you! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: chart for income expense' directly relates to the main feature: the income/expense pie chart added to the Transaction History screen. While the PR also includes a complete Android widget implementation and supporting infrastructure, the title captures the primary user-facing feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Nagarjuna0033

Copy link
Copy Markdown
Collaborator Author

@coderabbitai describe changes that are done in this pr in description

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of files changed in the PR
gh pr view 2052 --repo openMF/mifos-pay --json files --jq '.files[].path'
echo "---"
# Get the PR diff summary
gh pr diff 2052 --repo openMF/mifos-pay

Length of output: 126832


🏁 Script executed:

#!/bin/bash
gh pr edit 2052 --repo openMF/mifos-pay --body "## feat: chart for income expense + Android home screen widget

## Issue Fix
Jira Task: [MW-421](https://mifosforge.jira.com/browse/MW-421)

## Summary of Changes

This PR delivers two major features:

### 1. 🥧 Income / Expense Pie Chart on Transaction History Screen
- Added a **donut/pie chart** to the Transaction History screen that visualises the split between **Credits** and **Debits** for the current transaction list.
- Integrated the **Vico 3.2.2** charting library (\`com.patrykandpatrick.vico:compose\` + \`compose-m3\`) as a multiplatform dependency.
- Created a new \`expect/actual\` composable \`MifosPieIncomeExpense\` with concrete implementations for **Android**, **Desktop**, **JS**, and **WasmJs** targets; the **native** (iOS) target provides a no-op stub.
- Added a shared \`MifosLegendItem\` composable (in \`commonMain\`) that renders a coloured dot, label, currency value, and percentage for each chart segment.
- \`HistoryViewModel\` now calculates \`totalCredit\` and \`totalDebit\` from the loaded transaction list and exposes them via \`HistoryState\`.
- \`HistoryScreen\` wraps the chart and the existing transaction list in a \`Column\` so they appear together.

### 2. 📱 Android Home Screen Widget (Glance)
- Implemented a **Glance-based AppWidget** (\`FinanceGlanceWidget\`) that displays the user's available balance, account number, and quick-action buttons ("+ Income", "− Expense").
- The widget renders three states:
  - **Unauthenticated** – shows a "Sign In" call-to-action.
  - **Authenticated** – shows full balance details with action buttons (full width ≥ 250 dp) or a compact balance view (narrow).
  - **Error** – shows a retry / open-app prompt.
- Added \`FinanceWidgetReceiver\` to handle widget lifecycle (\`onEnabled\` / \`onDisabled\`) and schedule/cancel the background refresh.
- Added \`WidgetRefreshWorker\` (WorkManager \`CoroutineWorker\`) that runs every **15 minutes** on a network-connected device to keep balance data fresh.
- Integrated **WorkManager** + **Koin WorkManager Factory** for DI-aware worker injection.

### 3. 🔗 Deep Link Support (\`mifospay://\` scheme)
- Registered the \`mifospay://\` URI scheme in \`AndroidManifest.xml\`.
- Defined \`WidgetDeepLink\` constants (\`mifospay://dashboard\`, \`mifospay://add\`, \`mifospay://expense\`).
- Widget action callbacks (\`OpenDashboardAction\`, \`AddIncomeAction\`, \`AddExpenseAction\`) fire \`Intent.ACTION_VIEW\` intents to the respective deep link URIs.
- \`HomeNavigation\` now registers \`mifospay://dashboard\` as a nav deep link.
- Added multiplatform \`PendingDeepLinkStore\` + \`consumePendingDeepLink\` expect/actual infrastructure so incoming deep links (captured on app start / \`onNewIntent\`) are queued and consumed after the auth/passcode gate clears.

### 4. 🗃️ Widget Data Layer
| Component | Location | Purpose |
|---|---|---|
| \`WidgetData\` | \`core/model\` | Serializable data class holding balance, currency, account number, budget info |
| \`WidgetPreferenceDataSource\` | \`core/datastore\` | Persists \`WidgetData\` via multiplatform Settings |
| \`WidgetRepository\` / \`WidgetRepositoryImpl\` | \`core/data\` | Fetches live balance from \`AccountRepository\` and merges with stored data |
| \`WidgetManagerRepository\` / \`WidgetManagerRepositoryImpl\` | \`core/data\` | Orchestrates refresh, update, clear, schedule, cancel |
| \`WidgetSyncService\` / \`AndroidWidgetSyncService\` | \`core/data\` + \`cmp-android\` | Platform boundary for pushing data to the OS widget |
| \`WidgetDataProvider\` / \`WidgetDataProviderImpl\` | \`cmp-shared\` | Reactive \`Flow<WidgetState>\` consumed by \`FinanceGlanceWidget\` |

### 5. 🔄 Widget Refresh Triggers
The widget balance is automatically refreshed after:
- **Login** (\`LoginUseCase\`)
- **Logout** (\`MifosPayViewModel.logOut()\`)
- **Any outgoing transfer** (Make Transfer, Interbank Transfer, Intrabank/Third-Party Transfer)

### 6. 🛠️ Other / Infrastructure
- \`UserPreferencesDataSource.clearInfo()\` now also resets all in-memory \`MutableStateFlow\` fields so logout state is immediately reflected in collectors.
- \`MainActivity.onNewIntent\` override added to pass fresh intents to the active composable tree.
- \`MifosPayApp\` implements \`Configuration.Provider\` for manual WorkManager initialisation (required when disabling the default \`WorkManagerInitializer\`).

## Screenshots
<img width=\"350\" height=\"750\" alt=\"Transaction History with Pie Chart\" src=\"https://github.com/user-attachments/assets/cba57c0f-770c-43f7-9d6a-075d8a7b33f8\" />
"

Length of output: 5348


🏁 Script executed:

#!/bin/bash
cat > /tmp/pr_body.md << 'ENDBODY'
## feat: chart for income expense + Android home screen widget

## Issue Fix
Jira Task: [MW-421](https://mifosforge.jira.com/browse/MW-421)

## Summary of Changes

This PR delivers two major features alongside supporting infrastructure:

### 1. 🥧 Income / Expense Pie Chart on Transaction History Screen
- Added a **donut/pie chart** to the Transaction History screen that visualises the split between **Credits** and **Debits** for the currently loaded transaction list.
- Integrated the **Vico 3.2.2** charting library (`com.patrykandpatrick.vico:compose` + `compose-m3`) as a multiplatform dependency.
- Created a new `expect/actual` composable `MifosPieIncomeExpense` with concrete implementations for **Android**, **Desktop**, **JS**, and **WasmJs** targets; the **native** (iOS) target provides a no-op stub.
- Added a shared `MifosLegendItem` composable (in `commonMain`) that renders a coloured dot, label, currency value, and percentage for each chart segment.
- `HistoryViewModel` now calculates `totalCredit` and `totalDebit` from the loaded transaction list and exposes them via `HistoryState`.
- `HistoryScreen` wraps the chart and the existing transaction list together in a `Column`.

### 2. 📱 Android Home Screen Widget (Glance)
- Implemented a **Glance-based AppWidget** (`FinanceGlanceWidget`) that displays the user's available balance, masked account number, and quick-action buttons ("+ Income", "− Expense").
- The widget renders three distinct states:
  - **Unauthenticated** – shows a "Sign In" call-to-action.
  - **Authenticated** – shows full balance details with action buttons (width ≥ 250 dp) or a compact balance-only view (narrow).
  - **Error** – shows a retry / open-app prompt.
- Added `FinanceWidgetReceiver` (`GlanceAppWidgetReceiver`) to handle widget lifecycle (`onEnabled` / `onDisabled`) and schedule/cancel the background refresh.
- Added `WidgetRefreshWorker` (WorkManager `CoroutineWorker`) that runs every **15 minutes** when a network connection is available, keeping balance data fresh.
- Integrated **WorkManager** and **Koin WorkManager Factory** for DI-aware worker injection.

### 3. 🔗 Deep Link Support (`mifospay://` scheme)
- Registered the `mifospay://` URI scheme in `AndroidManifest.xml`.
- Defined `WidgetDeepLink` constants: `mifospay://dashboard`, `mifospay://add`, `mifospay://expense`.
- Widget tap callbacks (`OpenDashboardAction`, `AddIncomeAction`, `AddExpenseAction`) fire `Intent.ACTION_VIEW` intents to the respective deep link URIs.
- `HomeNavigation` now registers `mifospay://dashboard` as a Compose Navigation deep link.
- Added multiplatform `PendingDeepLinkStore` + `consumePendingDeepLink` expect/actual infrastructure so incoming deep links (captured on app start or `onNewIntent`) are queued and consumed only after the auth/passcode gate clears.

### 4. 🗃️ Widget Data Layer

| Component | Module | Purpose |
|---|---|---|
| `WidgetData` | `core/model` | Serializable data class: balance, currency, account number, budget fields |
| `WidgetPreferenceDataSource` | `core/datastore` | Persists `WidgetData` via multiplatform Settings |
| `WidgetRepository` / `WidgetRepositoryImpl` | `core/data` | Fetches live balance from `AccountRepository` and merges with stored data |
| `WidgetManagerRepository` / `WidgetManagerRepositoryImpl` | `core/data` | Orchestrates refresh, update, clear, schedule, cancel |
| `WidgetSyncService` / `AndroidWidgetSyncService` | `core/data` + `cmp-android` | Platform boundary for pushing data to the OS widget |
| `WidgetDataProvider` / `WidgetDataProviderImpl` | `cmp-shared` | Reactive `Flow<WidgetState>` consumed by `FinanceGlanceWidget` |

### 5. 🔄 Widget Refresh Triggers
The widget balance is automatically refreshed after:
- **Login** (`LoginUseCase`)
- **Logout** (`MifosPayViewModel.logOut()`)
- **Any outgoing transfer** (Make Transfer, Interbank Transfer, Intrabank/Third-Party Transfer)

### 6. 🛠️ Infrastructure / Fixes
- `UserPreferencesDataSource.clearInfo()` now also resets all in-memory `MutableStateFlow` fields so logout state is immediately reflected in all active collectors.
- `MainActivity.onNewIntent` override added to pass fresh intents into the active composable tree.
- `MifosPayApp` now implements `Configuration.Provider` for manual WorkManager initialisation (required after disabling the default `WorkManagerInitializer`).

## Screenshots
<img width="350" height="750" alt="Transaction History with Pie Chart" src="https://github.com/user-attachments/assets/cba57c0f-770c-43f7-9d6a-075d8a7b33f8" />
ENDBODY

gh pr edit 2052 --repo openMF/mifos-pay --body-file /tmp/pr_body.md
echo "Exit code: $?"

Length of output: 5095



@Nagarjuna0033 The automated PR description update was blocked by the repository's workflow restriction settings. You can copy and paste the description below directly into the PR edit form:


feat: chart for income expense + Android home screen widget

Issue Fix

Jira Task: MW-421

Summary of Changes

This PR delivers two major features alongside supporting infrastructure:

1. 🥧 Income / Expense Pie Chart on Transaction History Screen

  • Added a donut/pie chart to the Transaction History screen that visualises the split between Credits and Debits for the currently loaded transaction list.
  • Integrated the Vico 3.2.2 charting library (com.patrykandpatrick.vico:compose + compose-m3) as a multiplatform dependency.
  • Created a new expect/actual composable MifosPieIncomeExpense with concrete implementations for Android, Desktop, JS, and WasmJs targets; the native (iOS) target provides a no-op stub.
  • Added a shared MifosLegendItem composable (in commonMain) that renders a coloured dot, label, currency value, and percentage for each chart segment.
  • HistoryViewModel now calculates totalCredit and totalDebit from the loaded transaction list and exposes them via HistoryState.
  • HistoryScreen wraps the chart and the existing transaction list together in a Column.

2. 📱 Android Home Screen Widget (Glance)

  • Implemented a Glance-based AppWidget (FinanceGlanceWidget) that displays the user's available balance, masked account number, and quick-action buttons ("+ Income", "− Expense").
  • The widget renders three distinct states:
    • Unauthenticated – shows a "Sign In" call-to-action.
    • Authenticated – shows full balance details with action buttons (width ≥ 250 dp) or a compact balance-only view (narrow).
    • Error – shows a retry / open-app prompt.
  • Added FinanceWidgetReceiver (GlanceAppWidgetReceiver) to handle widget lifecycle (onEnabled / onDisabled) and schedule/cancel the background refresh.
  • Added WidgetRefreshWorker (WorkManager CoroutineWorker) that runs every 15 minutes when a network connection is available.
  • Integrated WorkManager + Koin WorkManager Factory for DI-aware worker injection.

3. 🔗 Deep Link Support (mifospay:// scheme)

  • Registered the mifospay:// URI scheme in AndroidManifest.xml.
  • Defined WidgetDeepLink constants: mifospay://dashboard, mifospay://add, mifospay://expense.
  • Widget tap callbacks (OpenDashboardAction, AddIncomeAction, AddExpenseAction) fire Intent.ACTION_VIEW intents to the respective deep link URIs.
  • HomeNavigation now registers mifospay://dashboard as a Compose Navigation deep link.
  • Added multiplatform PendingDeepLinkStore + consumePendingDeepLink expect/actual infrastructure so deep links are queued and consumed only after the auth/passcode gate clears.

4. 🗃️ Widget Data Layer

Component Module Purpose
WidgetData core/model Serializable data class: balance, currency, account number, budget fields
WidgetPreferenceDataSource core/datastore Persists WidgetData via multiplatform Settings
WidgetRepository / WidgetRepositoryImpl core/data Fetches live balance from AccountRepository and merges with stored data
WidgetManagerRepository / WidgetManagerRepositoryImpl core/data Orchestrates refresh, update, clear, schedule, cancel
WidgetSyncService / AndroidWidgetSyncService core/data + cmp-android Platform boundary for pushing data to the OS widget
WidgetDataProvider / WidgetDataProviderImpl cmp-shared Reactive Flow<WidgetState> consumed by FinanceGlanceWidget

5. 🔄 Widget Refresh Triggers

The widget balance is automatically refreshed after:

  • Login (LoginUseCase)
  • Logout (MifosPayViewModel.logOut())
  • Any outgoing transfer (Make Transfer, Interbank Transfer, Intrabank/Third-Party Transfer)

6. 🛠️ Infrastructure / Fixes

  • UserPreferencesDataSource.clearInfo() now also resets all in-memory MutableStateFlow fields so logout state is immediately reflected in all active collectors.
  • MainActivity.onNewIntent override added to pass fresh intents into the active composable tree.
  • MifosPayApp now implements Configuration.Provider for manual WorkManager initialisation (required after disabling the default WorkManagerInitializer).

Screenshots

Transaction History with Pie Chart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (6)
core/ui/src/androidMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.android.kt (1)

35-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider extracting shared pie-chart logic to reduce platform drift.

This implementation mirrors the desktop/js/wasm actuals. Moving common percentage/model/legend/chart setup into shared code (with minimal platform wrappers) would reduce maintenance overhead and behavior skew risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/ui/src/androidMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.android.kt`
around lines 35 - 107, The MifosPieIncomeExpense function contains logic for
calculating percentages, managing the model producer, and preparing legend data
that is likely duplicated across platform implementations. Extract the shared
calculation logic (totalCredit, totalDebit, creditPercentage, debitPercentage
percentage calculations and model producer setup) into common shared code as a
helper function or data class, and refactor the actual platform implementations
(Android, desktop, js, wasm) to use this shared logic while only handling
platform-specific UI composition. This approach centralizes the business logic
and reduces maintenance overhead across platform variants.
core/ui/src/desktopMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.desktop.kt (1)

35-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract shared pie-chart rendering logic to reduce platform drift.

This implementation is duplicated nearly line-for-line across desktop/js/wasm. Centralizing the shared chart + legend composition in common code (with thin platform wrappers if needed) will reduce divergence and future bug-fix overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/ui/src/desktopMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.desktop.kt`
around lines 35 - 107, The MifosPieIncomeExpense function implementation is
duplicated across desktop, js, and wasm platforms with nearly identical logic.
Extract the shared pie-chart rendering logic (modelProducer initialization,
percentage calculations, the Row with MifosLegendItem components, and the
PieChartHost with rememberPieChart configuration) into a common composable
function, then have each platform-specific implementation delegate to this
shared composable. This will centralize the chart and legend composition in
common code, reducing platform divergence and making future bug fixes apply
consistently across all platforms.
core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/WidgetPreferenceDataSource.kt (1)

33-42: 🚀 Performance & Scalability | 🔵 Trivial

This refactor would change error-handling behavior and is unsafe as proposed.

While the original code does read WIDGET_DATA_KEY twice (once in decodeValueOrNull and again in decodeValue), the duplicate parse is intentional, not redundant. The decodeValue call ensures errors are surfaced when stored data cannot be deserialized.

According to multiplatform-settings-serialization behavior:

  • decodeValue(..., defaultValue = ...) uses defaultValue only when the key is missing; if the key exists but deserialization fails, it throws an exception
  • decodeValueOrNull(...) returns null on deserialization failure and silently catches the error

The current nested approach means: if corrupted data exists, decodeValue will throw. The proposed single decodeValueOrNull call would instead silently return DEFAULT, masking data corruption that should be logged or handled explicitly.

To safely eliminate the duplicate read while preserving error handling, use decodeValueOrNull only if you intend to treat corrupted persisted data the same as missing data. Otherwise, keep the current structure or handle the exception path explicitly at the call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/WidgetPreferenceDataSource.kt`
around lines 33 - 42, The initialization of _widgetData in the
WidgetPreferenceDataSource class contains intentional nested calls to read
WIDGET_DATA_KEY. Do not simplify this by using a single decodeValueOrNull call,
as that would change error-handling behavior and mask data corruption. The
current approach using decodeValue with a defaultValue parameter ensures that if
the key exists but deserialization fails, an exception is thrown to surface the
error. If you must refactor, either preserve the current error-throwing behavior
with decodeValue, or explicitly handle deserialization exceptions at the call
site before substituting with a single decodeValueOrNull call that would
silently treat corrupted data as missing data.
core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/di/PreferenceModule.kt (1)

66-71: 🗄️ Data Integrity & Integration | 🔵 Trivial

Consider binding WidgetPreferenceDataSource as single to ensure shared stream semantics.

This datasource owns a MutableStateFlow (lines 33–44 of WidgetPreferenceDataSource.kt). With factory scope, each injection creates a new instance and a separate in-memory stream. Currently, only WidgetRepositoryImpl injects it (and WidgetRepositoryImpl itself is single), so there is effectively only one instance in the app. However, if other classes directly inject WidgetPreferenceDataSource in the future, they would receive separate streams with no shared state. Binding it as single would prevent this unintended behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/di/PreferenceModule.kt`
around lines 66 - 71, The WidgetPreferenceDataSource binding in the factory
block should be changed to single scope to ensure all consumers share the same
MutableStateFlow instance and maintain consistent stream semantics. Replace the
factory scope with single scope for the WidgetPreferenceDataSource binding in
PreferenceModule.kt so that regardless of how many classes inject it in the
future, they will always receive the same instance and share the same state
stream.
core/data/src/commonMain/kotlin/org/mifospay/core/data/util/WidgetSyncService.kt (1)

30-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify refreshWidget() semantics to match implementation.

Line 31–33 says this path “does not touch storage,” but Android’s implementation invalidates the provider before redraw, which can trigger a fresh data load. Please align this KDoc with real behavior (or split invalidate vs redraw responsibilities explicitly).

♻️ Suggested KDoc adjustment
     /**
-     * Re-render all placed widget instances from whatever is already in storage.
-     * Does not touch storage — just triggers a Glance / WidgetKit redraw.
+     * Trigger a widget refresh cycle for all placed instances.
+     * Implementations may invalidate cached/provider data first, then request redraw
+     * (e.g., Glance / WidgetKit update).
      */
     suspend fun refreshWidget()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/util/WidgetSyncService.kt`
around lines 30 - 34, The KDoc for the refreshWidget() function incorrectly
states that it does not touch storage and only triggers a redraw, but the actual
Android implementation invalidates the provider before redrawing, which can
trigger fresh data loads from storage. Update the KDoc comment for
refreshWidget() to accurately reflect this behavior by clarifying that the
function both invalidates the provider (which may trigger fresh data loads) and
triggers a redraw, rather than claiming it leaves storage untouched.
Alternatively, if the intent is truly to only redraw without data refreshes,
refactor the implementation to separate the invalidate and redraw
responsibilities into distinct functions.
cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt (1)

108-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Externalize widget UI copy into string resources.

Multiple user-facing labels are hardcoded (for example at Line 109, Line 130, Line 198, Line 221). Move them to strings.xml for localization and copy consistency.

Also applies to: 184-221, 255-266, 295-296, 331-356, 413-445

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt`
around lines 108 - 141, Move all hardcoded user-facing text strings from the
FinanceGlanceWidget composable to strings.xml resources. Replace hardcoded
strings like "MifosPay", "Digital Wallet", "Welcome Back", and "Sign in to view
balances and manage your wallet" throughout the entire widget implementation
(including the sections at lines 108-141, 184-221, 255-266, 295-296, 331-356,
and 413-445) with stringResource() calls that reference the corresponding string
resource identifiers. Define each unique string resource in strings.xml with
appropriate resource names and use the stringResource() function to retrieve
them at runtime for localization and consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmp-android/build.gradle.kts`:
- Around line 135-137: The hardcoded implementation of
`io.insert-koin:koin-android:3.5.6` on line 136 conflicts with the
catalog-managed version at line 114. Remove this hardcoded declaration and
replace it with a reference to the catalog-managed version (libs.koin.android).
Additionally, instead of hardcoding
`io.insert-koin:koin-androidx-workmanager:3.5.6`, add this dependency to the
version catalog and use the catalog reference to maintain a single source of
truth for all dependency versions.

In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.txt`:
- Line 121: The androidx.glance dependency is currently pinned to version 1.1.0,
which has a known CVE-2024-7254 vulnerability due to its use of protobuf 4.28.2.
Locate all occurrences of androidx.glance:1.1.0 in the dependency configuration
(mentioned as being present at multiple locations including line 157 and related
areas) and upgrade them to androidx.glance:1.1.1 or a later stable version to
resolve the security vulnerability.

In `@cmp-android/src/main/AndroidManifest.xml`:
- Around line 54-61: The intent-filter in the AndroidManifest.xml currently
accepts any host under the `mifospay://` scheme by only specifying the scheme in
the `<data>` element. To restrict deep-linking to only the hosts actually used
by the widget, replace the single `<data android:scheme="mifospay" />` element
with multiple `<data>` elements that each specify both the `android:scheme`
attribute set to "mifospay" and the `android:host` attribute set to one of the
three allowed hosts: "add", "expense", and "dashboard". This will limit the
app's externally triggerable entry points to only those hosts needed for widget
functionality.

In `@cmp-android/src/main/kotlin/org/mifospay/MifosPayApp.kt`:
- Around line 48-52: In the MifosPayApp class where
WorkManager.getInstance(this).enqueue() is called with OneTimeWorkRequestBuilder
for WidgetRefreshWorker, replace the enqueue() method with enqueueUniqueWork()
instead. This method requires two additional parameters: a unique work name
string identifier for this refresh job, and ExistingWorkPolicy.KEEP to prevent
scheduling duplicate work if the job already exists. This ensures the widget
refresh worker is only scheduled once during the app lifecycle rather than on
every app startup.

In `@cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt`:
- Around line 30-45: There is a duplicate and conflicting import of
ColorProvider class in the FinanceGlanceWidget.kt file. The import from
androidx.glance.color package (appearing earlier in the imports) is incorrect
and conflicts with the proper import from androidx.glance.unit package. Remove
the ColorProvider import from androidx.glance.color and keep only the
ColorProvider import from androidx.glance.unit to resolve the ambiguous symbol
compilation error.

In `@cmp-android/src/main/kotlin/org/mifospay/widget/WidgetRefreshWorker.kt`:
- Around line 35-42: The catch block in the doWork() method catches all
exceptions and unconditionally calls Result.retry(), which causes permanent
errors to retry forever and prevents proper coroutine cancellation. Modify the
catch block to first check if the caught exception is a CancellationException
and re-throw it to preserve cancellation semantics. For other exceptions,
implement logic to distinguish between transient failures (like network errors)
that should be retried with Result.retry(), and permanent failures that should
fail with Result.failure() instead of retrying indefinitely.

In
`@cmp-shared/src/androidMain/kotlin/org/mifospay/shared/navigation/ConsumePendingDeepLink.android.kt`:
- Around line 23-25: The issue is that PendingDeepLinkStore.consume() is called
before checking if handleDeepLink() succeeds, causing the URI to be permanently
lost if navigation fails. Refactor the logic to check the boolean return value
of navController.handleDeepLink(intent) and only consume the URI from
PendingDeepLinkStore if handleDeepLink returns true. If handleDeepLink returns
false, the URI should remain in the store so it can be retried later.

In `@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayViewModel.kt`:
- Line 88: The call to widgetManagerRepository.clear() at line 88 can throw an
exception that halts execution and prevents subsequent lock and passcode cleanup
operations from running. Wrap the widgetManagerRepository.clear() call in a
try-catch block to handle any potential exceptions gracefully, allowing the
cleanup process to continue even if the widget manager clear operation fails.
Log any caught exceptions for debugging purposes but ensure the logout flow
proceeds to complete remaining cleanup tasks.

In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/widget/WidgetDataProviderImpl.kt`:
- Around line 39-43: The runCatching block around
widgetRepository.getWidgetData() is catching CancellationException and
converting it to WidgetState.Error, which violates the coroutine cancellation
contract. Instead of using runCatching, implement a try-catch block that catches
the exception and explicitly checks if it is a CancellationException using
isA<CancellationException> or similar, then rethrows it to allow proper
cancellation propagation while still catching other exceptions and mapping them
to WidgetState.Error. This ensures cancellation works correctly without breaking
the Flow contract.

In
`@cmp-shared/src/nonAndroidMain/kotlin/org/mifospay/shared/navigation/PendingDeepLinkStore.nonAndroid.kt`:
- Around line 16-18: The consume() function in the non-Android implementation
uses TODO() which throws a runtime exception instead of safely handling the
non-Android platform case. Replace the TODO("Not yet implemented") statement
with a simple return null to match the common contract of the nullable String?
return type, allowing the function to safely no-op on non-Android platforms
without crashing.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/di/RepositoryModule.kt`:
- Around line 160-173: The WidgetManagerRepository binding in the common
RepositoryModule depends on widgetSyncService which is only provided by the
Android-specific androidWidgetModule, causing dependency resolution failures on
non-Android platforms. To fix this, either create a no-op implementation of
WidgetSyncService and add a fallback binding for it in the common
RepositoryModule that works on all platforms, or create platform-specific
modules for each non-Android target (Desktop, iOS, JS, WASM, Native) that
provide their own WidgetSyncService binding. The key is ensuring that whenever
WidgetManagerRepository is instantiated with get() for widgetSyncService on any
platform, a WidgetSyncService binding is available in the Koin DI container.

In `@core/domain/src/commonMain/kotlin/org/mifospay/core/domain/LoginUseCase.kt`:
- Line 69: The widgetManagerRepository.refresh() call at line 69 in the
LoginUseCase can throw an exception that aborts the entire login process even
though user data has already been successfully persisted. Wrap the
widgetManagerRepository.refresh() invocation in a try-catch block to handle any
exceptions gracefully, log any errors that occur, and allow the login process to
complete successfully regardless of whether the widget refresh succeeds or
fails.

In
`@core/ui/src/androidMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.android.kt`:
- Around line 100-102: The PieValueFormatter in the valueFormatter block is
using toInt() which truncates decimal percentages instead of rounding them,
causing visual inconsistency with the legend. Replace the toInt() call with
proper rounding logic, such as using round() function and then converting to
Int, or use String.format() with one decimal place to preserve accuracy and
match the legend percentages displayed to users.

In
`@feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt`:
- Around line 145-149: The TransactionList composable is receiving the full
outer modifier which can cause it to claim the entire height and overlap with
the chart above it. Instead of passing the full modifier directly to
TransactionList, apply a modifier that constrains it to only the remaining space
available in the Column after the chart. Use Compose layout modifiers like
weight(1f) combined with fillMaxWidth() to ensure the list takes only the
remaining vertical space while maintaining proper width behavior.

In
`@feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt`:
- Line 78: The calculateSummary() function in HistoryViewModel is only called
once during initial transaction fetch, so when applyFilter() updates the
filtered transaction list, the totalCredit and totalDebit values are not
recalculated. This causes the chart to display totals from all transactions
while the list shows only filtered ones. Within the applyFilter() method (lines
185-199), after updating the filtered transaction list, add a call to
calculateSummary() passing the filtered transactions to ensure totalCredit and
totalDebit reflect the current filtered data set displayed to the user.

In
`@feature/make-transfer/src/commonMain/kotlin/org/mifospay/feature/make/transfer/MakeTransferViewModel.kt`:
- Line 192: The viewModelScope.launch block containing
widgetManagerRepository.refresh() lacks error handling, which means any
exception thrown during the refresh operation can crash the ViewModel scope and
interrupt the successful transfer flow. Wrap the
widgetManagerRepository.refresh() call in a try-catch block to gracefully handle
any exceptions that may occur during the refresh operation, ensuring the success
flow continues uninterrupted even if the widget refresh fails. Optionally log
the exception for debugging purposes while preventing it from propagating to
crash the scope.

In
`@feature/transfer-interbank/src/commonMain/kotlin/org/mifospay/feature/transfer/interbank/InterbankTransferViewModel.kt`:
- Around line 367-369: The widgetManagerRepository.refresh() call inside the
viewModelScope.launch block can throw an exception that would fail the coroutine
after the transfer has already succeeded, negatively impacting the user
experience. Wrap the widgetManagerRepository.refresh() call in a try-catch block
to defensively handle any exceptions it might throw, ensuring that widget
refresh failures do not interrupt or affect the successful transfer completion
flow.

In
`@feature/transfer-intrabank/src/commonMain/kotlin/org/mifospay/feature/transfer/intrabank/confirm/TransferConfirmViewModel.kt`:
- Line 343: The widgetManagerRepository.refresh() call in the success branch of
TransferConfirmViewModel lacks error isolation, which means any exception thrown
during refresh can fail the entire flow even after the transfer has been
successfully committed. Wrap the refresh() call in a try-catch block to catch
and handle any exceptions gracefully without propagating them up the call stack,
ensuring that widget refresh failures do not impact the already-committed
transfer success state.

In `@gradle/libs.versions.toml`:
- Around line 143-144: The gradle/libs.versions.toml file contains a duplicate
`[libraries]` table header declaration on consecutive lines, which is invalid
TOML syntax and breaks version-catalog parsing. Remove one of the duplicate
`[libraries]` table headers so that only a single instance remains in the file.

---

Nitpick comments:
In `@cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt`:
- Around line 108-141: Move all hardcoded user-facing text strings from the
FinanceGlanceWidget composable to strings.xml resources. Replace hardcoded
strings like "MifosPay", "Digital Wallet", "Welcome Back", and "Sign in to view
balances and manage your wallet" throughout the entire widget implementation
(including the sections at lines 108-141, 184-221, 255-266, 295-296, 331-356,
and 413-445) with stringResource() calls that reference the corresponding string
resource identifiers. Define each unique string resource in strings.xml with
appropriate resource names and use the stringResource() function to retrieve
them at runtime for localization and consistency.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/util/WidgetSyncService.kt`:
- Around line 30-34: The KDoc for the refreshWidget() function incorrectly
states that it does not touch storage and only triggers a redraw, but the actual
Android implementation invalidates the provider before redrawing, which can
trigger fresh data loads from storage. Update the KDoc comment for
refreshWidget() to accurately reflect this behavior by clarifying that the
function both invalidates the provider (which may trigger fresh data loads) and
triggers a redraw, rather than claiming it leaves storage untouched.
Alternatively, if the intent is truly to only redraw without data refreshes,
refactor the implementation to separate the invalidate and redraw
responsibilities into distinct functions.

In
`@core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/di/PreferenceModule.kt`:
- Around line 66-71: The WidgetPreferenceDataSource binding in the factory block
should be changed to single scope to ensure all consumers share the same
MutableStateFlow instance and maintain consistent stream semantics. Replace the
factory scope with single scope for the WidgetPreferenceDataSource binding in
PreferenceModule.kt so that regardless of how many classes inject it in the
future, they will always receive the same instance and share the same state
stream.

In
`@core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/WidgetPreferenceDataSource.kt`:
- Around line 33-42: The initialization of _widgetData in the
WidgetPreferenceDataSource class contains intentional nested calls to read
WIDGET_DATA_KEY. Do not simplify this by using a single decodeValueOrNull call,
as that would change error-handling behavior and mask data corruption. The
current approach using decodeValue with a defaultValue parameter ensures that if
the key exists but deserialization fails, an exception is thrown to surface the
error. If you must refactor, either preserve the current error-throwing behavior
with decodeValue, or explicitly handle deserialization exceptions at the call
site before substituting with a single decodeValueOrNull call that would
silently treat corrupted data as missing data.

In
`@core/ui/src/androidMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.android.kt`:
- Around line 35-107: The MifosPieIncomeExpense function contains logic for
calculating percentages, managing the model producer, and preparing legend data
that is likely duplicated across platform implementations. Extract the shared
calculation logic (totalCredit, totalDebit, creditPercentage, debitPercentage
percentage calculations and model producer setup) into common shared code as a
helper function or data class, and refactor the actual platform implementations
(Android, desktop, js, wasm) to use this shared logic while only handling
platform-specific UI composition. This approach centralizes the business logic
and reduces maintenance overhead across platform variants.

In
`@core/ui/src/desktopMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.desktop.kt`:
- Around line 35-107: The MifosPieIncomeExpense function implementation is
duplicated across desktop, js, and wasm platforms with nearly identical logic.
Extract the shared pie-chart rendering logic (modelProducer initialization,
percentage calculations, the Row with MifosLegendItem components, and the
PieChartHost with rememberPieChart configuration) into a common composable
function, then have each platform-specific implementation delegate to this
shared composable. This will centralize the chart and legend composition in
common code, reducing platform divergence and making future bug fixes apply
consistently across all platforms.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4621740-fbc8-4ee0-b145-7e880935f409

📥 Commits

Reviewing files that changed from the base of the PR and between a518db7 and 2526ac4.

📒 Files selected for processing (58)
  • cmp-android/build.gradle.kts
  • cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt
  • cmp-android/dependencies/prodReleaseRuntimeClasspath.txt
  • cmp-android/src/main/AndroidManifest.xml
  • cmp-android/src/main/kotlin/org/mifospay/MainActivity.kt
  • cmp-android/src/main/kotlin/org/mifospay/MifosPayApp.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/AndroidWidgetModule.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/AndroidWidgetSyncService.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/FinanceWidgetReceiver.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/WidgetDeepLink.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/WidgetRefreshWorker.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/ui/Actions.kt
  • cmp-android/src/main/kotlin/org/mifospay/widget/ui/WidgetAction.kt
  • cmp-android/src/main/res/values/strings.xml
  • cmp-android/src/main/res/xml/finance_widget_info.xml
  • cmp-shared/src/androidMain/kotlin/org/mifospay/shared/MifosPayApp.android.kt
  • cmp-shared/src/androidMain/kotlin/org/mifospay/shared/navigation/ConsumePendingDeepLink.android.kt
  • cmp-shared/src/androidMain/kotlin/org/mifospay/shared/navigation/PendingDeepLinkStore.android.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayViewModel.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/ConsumePendingDeepLink.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/PendingDeepLinkStore.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/RootNavGraph.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/widget/WidgetDataProvider.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/widget/WidgetDataProviderImpl.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/widget/WidgetState.kt
  • cmp-shared/src/iosMain/kotlin/MifosViewController.kt
  • cmp-shared/src/nonAndroidMain/kotlin/org/mifospay/shared/MifosPayApp.nonAndroid.kt
  • cmp-shared/src/nonAndroidMain/kotlin/org/mifospay/shared/navigation/ConsumePendingDeepLink.nonAndroid.kt
  • cmp-shared/src/nonAndroidMain/kotlin/org/mifospay/shared/navigation/PendingDeepLinkStore.nonAndroid.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/di/RepositoryModule.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/WidgetManagerRepository.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/WidgetRepository.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/WidgetManagerRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/WidgetRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/util/WidgetSyncService.kt
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesDataSource.kt
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/WidgetPreferenceDataSource.kt
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/di/PreferenceModule.kt
  • core/domain/src/commonMain/kotlin/org/mifospay/core/domain/LoginUseCase.kt
  • core/domain/src/commonMain/kotlin/org/mifospay/core/domain/di/DomainModule.kt
  • core/model/src/commonMain/kotlin/org/mifospay/core/model/widget/WidgetData.kt
  • core/ui/build.gradle.kts
  • core/ui/src/androidMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.android.kt
  • core/ui/src/commonMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.kt
  • core/ui/src/desktopMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.desktop.kt
  • core/ui/src/jsMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.js.kt
  • core/ui/src/nativeMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.native.kt
  • core/ui/src/wasmJsMain/kotlin/org/mifospay/core/ui/MifosIncomeExpenseVisual.wasmJs.kt
  • feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt
  • feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt
  • feature/home/src/commonMain/kotlin/org/mifospay/feature/home/navigation/HomeNavigation.kt
  • feature/make-transfer/src/commonMain/kotlin/org/mifospay/feature/make/transfer/MakeTransferViewModel.kt
  • feature/transfer-interbank/src/commonMain/kotlin/org/mifospay/feature/transfer/interbank/InterbankTransferViewModel.kt
  • feature/transfer-intrabank/src/commonMain/kotlin/org/mifospay/feature/transfer/intrabank/confirm/TransferConfirmViewModel.kt
  • gradle/libs.versions.toml

Comment on lines +135 to +137
implementation("androidx.work:work-runtime-ktx:2.9.1")
implementation("io.insert-koin:koin-android:3.5.6")
implementation("io.insert-koin:koin-androidx-workmanager:3.5.6")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the build.gradle.kts file
find . -name "build.gradle.kts" -path "*cmp-android*" | head -5

Repository: openMF/mifos-pay

Length of output: 185


🏁 Script executed:

# Get file size and check specific lines
wc -l cmp-android/build.gradle.kts

Repository: openMF/mifos-pay

Length of output: 187


🏁 Script executed:

# Read the relevant section around lines 114-137
sed -n '110,145p' cmp-android/build.gradle.kts

Repository: openMF/mifos-pay

Length of output: 1334


🏁 Script executed:

# Check for version catalog file
find . -name "libs.versions.toml" -o -name "gradle.properties" | head -10

Repository: openMF/mifos-pay

Length of output: 234


🏁 Script executed:

# Search for all koin dependency declarations in the build file
rg "koin" cmp-android/build.gradle.kts -B 1 -A 1

Repository: openMF/mifos-pay

Length of output: 631


🏁 Script executed:

# Examine the version catalog
cat gradle/libs.versions.toml

Repository: openMF/mifos-pay

Length of output: 29711


🏁 Script executed:

# Search for koin versions in the catalog
rg "koin" gradle/libs.versions.toml -B 1 -A 1

Repository: openMF/mifos-pay

Length of output: 2077


Remove the hardcoded koin-android duplicate—it conflicts with the catalog version.

Lines 135-136 redeclare io.insert-koin:koin-android:3.5.6 which conflicts with the catalog-managed libs.koin.android (version 4.1.1, declared at line 114). Remove the hardcoded declarations and add missing entries (Glance, WorkManager, koin-androidx-workmanager) to the version catalog instead to maintain a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/build.gradle.kts` around lines 135 - 137, The hardcoded
implementation of `io.insert-koin:koin-android:3.5.6` on line 136 conflicts with
the catalog-managed version at line 114. Remove this hardcoded declaration and
replace it with a reference to the catalog-managed version (libs.koin.android).
Additionally, instead of hardcoding
`io.insert-koin:koin-androidx-workmanager:3.5.6`, add this dependency to the
version catalog and use the catalog reference to maintain a single source of
truth for all dependency versions.

androidx.concurrent:concurrent-futures-ktx:1.1.0
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.17.0
androidx.core:core-remoteviews:1.1.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Security vulnerabilities for androidx.glance 1.1.0, androidx.work 2.10.3, androidx.room 2.6.1, androidx.sqlite 2.4.0

💡 Result:

Security vulnerabilities for these exact versions (evidence-backed from the sources I found): A) androidx.glance 1.1.0 The official Glance release notes indicate that version 1.1.0 has a vulnerability risk related to CVE-2024-7254: Glance “compiles against protobuf 4.28.2 in order to address CVE-2024-7254,” and the notes instruct upgrading from version 1.1.0 to 1.1.1 “to address the vulnerability risk.” [1][2] B) androidx.work 2.10.3 In the sources retrieved for the WorkManager 2.10.3 release notes, I did not find an explicit CVE/security vulnerability associated with version 2.10.3 (only bug-fix text appeared in the retrieved snippet). [3] C) androidx.room 2.6.1 In the sources retrieved for the Room 2.6.1 release notes, I did not find an explicit CVE/security vulnerability associated with version 2.6.1 (only bug-fix text appeared in the retrieved snippet). [4] D) androidx.sqlite 2.4.0 In the sources retrieved for the SQLite 2.4.0 release notes, I did not find an explicit CVE/security vulnerability associated with version 2.4.0 (the retrieved snippet discussed bug fixes and a change-log, without mentioning CVEs). [5][6] Recommendation (based on the explicit evidence found): - For androidx.glance, upgrade to 1.1.1+ to address the protobuf vulnerability risk referenced as CVE-2024-7254 in the official release notes. [1][2] Note: To produce a fully complete “all CVEs that affect these artifacts/versions” list, I would normally also cross-check AndroidX/security advisory databases and/or dependency-advisory feeds for each specific artifact (and its sub-artifacts). If you share your exact Gradle coordinates for each library (some artifacts—especially Room/SQLite—have multiple modules), I can narrow the search to the correct components and try again.

Citations:


Upgrade androidx.glance from 1.1.0 to 1.1.1 or later to address CVE-2024-7254.

The resolved dependency lock file includes androidx.glance:1.1.0 (line 157 and related), which has a known vulnerability related to an unsafe protobuf version. According to the official AndroidX release notes, version 1.1.0 compiles against protobuf 4.28.2 which has CVE-2024-7254, and the fix requires upgrading to androidx.glance:1.1.1 or later.

The remaining dependencies align with PR objectives:

  • androidx.core:core-remoteviews (1.1.0) and upgraded androidx.glance (1.1.1+) for Glance widget UI
  • androidx.work (2.10.3) + io.insert-koin:koin-androidx-workmanager (4.1.1) for WorkManager-based refresh scheduling
  • androidx.room (2.6.1) + androidx.sqlite (2.4.0) for persistence (no CVE found)

All other versions are stable and compatible with the existing Compose BOM (2025.12.01) and Koin BOM (4.1.1).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.txt` at line 121, The
androidx.glance dependency is currently pinned to version 1.1.0, which has a
known CVE-2024-7254 vulnerability due to its use of protobuf 4.28.2. Locate all
occurrences of androidx.glance:1.1.0 in the dependency configuration (mentioned
as being present at multiple locations including line 157 and related areas) and
upgrade them to androidx.glance:1.1.1 or a later stable version to resolve the
security vulnerability.

Comment on lines +54 to +61
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="mifospay" />
</intent-filter>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the AndroidManifest.xml file
find . -name "AndroidManifest.xml" -type f

Repository: openMF/mifos-pay

Length of output: 1748


🏁 Script executed:

# Search for all occurrences of "mifospay" in the codebase to understand actual usage
rg "mifospay" --type kotlin --type java --type xml -i

Repository: openMF/mifos-pay

Length of output: 154


🏁 Script executed:

# Look for intent-filter and deep-link configurations in the Android manifest
cat cmp-android/src/main/AndroidManifest.xml | head -100

Repository: openMF/mifos-pay

Length of output: 3933


🏁 Script executed:

# Search for "mifospay" without type restrictions to find all usages
rg "mifospay" -i --no-heading

Repository: openMF/mifos-pay

Length of output: 154


🏁 Script executed:

# Search for navigation patterns that might use the mifospay scheme
rg "navigate.*mifospay|mifospay.*navigate" -i

Repository: openMF/mifos-pay

Length of output: 154


🏁 Script executed:

# Look for URI or Intent construction related to deep-links
rg "uri|intent|deeplink" cmp-android/src/main --type-list

Repository: openMF/mifos-pay

Length of output: 544


🏁 Script executed:

# Search for "dashboard", "add", "expense" as potential hosts in navigation code
rg "dashboard|\"add\"|expense" cmp-android/src --type-list -i

Repository: openMF/mifos-pay

Length of output: 544


🏁 Script executed:

# Simple search for mifospay in all files
rg "mifospay" .

Repository: openMF/mifos-pay

Length of output: 50373


🏁 Script executed:

# List Kotlin and Java files in cmp-android to understand code structure
find cmp-android -name "*.kt" -o -name "*.java" | head -20

Repository: openMF/mifos-pay

Length of output: 824


🏁 Script executed:

# Search for navigation patterns and deep-link URIs
rg "deeplink|DeepLink|NavDeepLink|navDeepLink" cmp-android --type-list

Repository: openMF/mifos-pay

Length of output: 544


🏁 Script executed:

# Look for strings that might contain the scheme or hosts
rg "dashboard|add.*expense" cmp-android -i

Repository: openMF/mifos-pay

Length of output: 2024


🏁 Script executed:

# Read the WidgetDeepLink.kt file to see all URI constants
cat cmp-android/src/main/kotlin/org/mifospay/widget/WidgetDeepLink.kt

Repository: openMF/mifos-pay

Length of output: 716


🏁 Script executed:

# Search for any other deep-link URI patterns that might use mifospay scheme
rg "mifospay://" cmp-android

Repository: openMF/mifos-pay

Length of output: 154


Restrict deep-link hosts to only those used by the widget.

The manifest currently accepts any host under the mifospay:// scheme. The app only uses three specific hosts (add, expense, and dashboard) for widget actions. Restricting the intent-filter to these hosts reduces the app's externally triggerable entry points to only those needed for widget functionality.

Suggested hardening
-                <data android:scheme="mifospay" />
+                <data android:scheme="mifospay" android:host="dashboard" />
+                <data android:scheme="mifospay" android:host="add" />
+                <data android:scheme="mifospay" android:host="expense" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/src/main/AndroidManifest.xml` around lines 54 - 61, The
intent-filter in the AndroidManifest.xml currently accepts any host under the
`mifospay://` scheme by only specifying the scheme in the `<data>` element. To
restrict deep-linking to only the hosts actually used by the widget, replace the
single `<data android:scheme="mifospay" />` element with multiple `<data>`
elements that each specify both the `android:scheme` attribute set to "mifospay"
and the `android:host` attribute set to one of the three allowed hosts: "add",
"expense", and "dashboard". This will limit the app's externally triggerable
entry points to only those hosts needed for widget functionality.

Comment on lines +48 to +52
WorkManager.getInstance(this)
.enqueue(
OneTimeWorkRequestBuilder<WidgetRefreshWorker>()
.build(),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n cmp-android/src/main/kotlin/org/mifospay/MifosPayApp.kt | head -60

Repository: openMF/mifos-pay

Length of output: 2677


🏁 Script executed:

# Check for any imports related to WorkManager
grep -n "import.*work\|WorkManager\|ExistingWorkPolicy" cmp-android/src/main/kotlin/org/mifospay/MifosPayApp.kt

Repository: openMF/mifos-pay

Length of output: 449


🏁 Script executed:

# Search for other uses of enqueueUniqueWork in the codebase to see if this pattern is used elsewhere
rg "enqueueUniqueWork" cmp-android/ --type kotlin

Repository: openMF/mifos-pay

Length of output: 154


🏁 Script executed:

# Check what widgetManager.schedule() does
rg "fun schedule\|\.schedule\(\)" cmp-android/src/main/kotlin/org/mifospay/core/data/repository/ --type kotlin -A 5

Repository: openMF/mifos-pay

Length of output: 257


🌐 Web query:

WorkManager enqueue vs enqueueUniqueWork behavior Android duplicate jobs on app startup

💡 Result:

Using WorkManager.enqueue() at app startup—such as inside an Activity's onCreate()—will cause duplicate jobs to be scheduled every time that code path executes [1][2][3]. Because enqueue() does not check for existing work, it simply adds a new instance of the WorkRequest to the queue [4][5][2]. To prevent duplicate jobs on app startup, you should use unique work APIs instead [4][6]. These methods identify work by a string name and allow you to define a policy for handling existing work with that same name [7][6]. The recommended approach is to use: 1. WorkManager.enqueueUniqueWork() for one-time work [4][6]. 2. WorkManager.enqueueUniquePeriodicWork() for periodic work [4][6]. When using these methods, you must provide an ExistingWorkPolicy (for one-time work) or ExistingPeriodicWorkPolicy (for periodic work) to determine how WorkManager should resolve collisions [4][6]. Common policies include: - KEEP: If work with the given name already exists, ignore the new request and keep the existing work [4][8]. - REPLACE: Cancel and delete the existing work, then enqueue the new work [4][8]. - APPEND: Add the new work as a prerequisite to the existing work sequence [4][8]. By choosing the appropriate policy—most commonly KEEP—you ensure that your startup logic only schedules the task if it is not already pending or running, effectively preventing duplicates [4][9].

Citations:


🏁 Script executed:

# Check if there are any other WorkManager enqueue calls in the codebase
rg "\.enqueue\(|\.enqueueUniqueWork\(" cmp-android/ --type kotlin -B 2 -A 2

Repository: openMF/mifos-pay

Length of output: 590


Use enqueueUniqueWork() to prevent duplicate startup refresh jobs.

enqueue() in Application.onCreate() schedules a new job every time the app starts. Use enqueueUniqueWork() with ExistingWorkPolicy.KEEP to ensure the refresh job is only scheduled once, preventing duplicate work from consuming resources and impacting performance.

Suggested fix
+import androidx.work.ExistingWorkPolicy
...
         WorkManager.getInstance(this)
-            .enqueue(
+            .enqueueUniqueWork(
+                "mifospay_widget_refresh_startup",
+                ExistingWorkPolicy.KEEP,
                 OneTimeWorkRequestBuilder<WidgetRefreshWorker>()
                     .build(),
             )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/src/main/kotlin/org/mifospay/MifosPayApp.kt` around lines 48 -
52, In the MifosPayApp class where WorkManager.getInstance(this).enqueue() is
called with OneTimeWorkRequestBuilder for WidgetRefreshWorker, replace the
enqueue() method with enqueueUniqueWork() instead. This method requires two
additional parameters: a unique work name string identifier for this refresh
job, and ExistingWorkPolicy.KEEP to prevent scheduling duplicate work if the job
already exists. This ensures the widget refresh worker is only scheduled once
during the app lifecycle rather than on every app startup.

Comment on lines +30 to +45
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.width
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import org.koin.core.component.KoinComponent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt"

echo "Inspect imports around ColorProvider:"
nl -ba "$FILE" | sed -n '24,50p'

echo
echo "Find ColorProvider import usage across Kotlin sources:"
rg -n --type=kotlin 'import androidx\.glance\.(color|unit)\.ColorProvider' .

Repository: openMF/mifos-pay

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt"

echo "=== Check if file exists ==="
if [ -f "$FILE" ]; then
    echo "File found: $FILE"
    wc -l "$FILE"
else
    echo "File NOT found at: $FILE"
    exit 1
fi

echo
echo "=== Inspect imports (lines 20-50) ==="
cat -n "$FILE" | sed -n '20,50p'

echo
echo "=== Search for ColorProvider imports in this file ==="
cat -n "$FILE" | grep -n "ColorProvider"

echo
echo "=== Check for all ColorProvider imports in project ==="
rg -n 'import.*ColorProvider' --type=kotlin cmp-android/

echo
echo "=== Check ColorProvider usage in FinanceGlanceWidget.kt ==="
rg -n 'ColorProvider' --type=kotlin "$FILE"

Repository: openMF/mifos-pay

Length of output: 4308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for Glance dependency in build files ==="
find cmp-android -name "*.gradle" -o -name "*.gradle.kts" | head -5

echo
echo "=== Check for Glance version in gradle files ==="
rg -n "glance" cmp-android/build.gradle* 2>/dev/null || rg -n "glance" . --type=gradle 2>/dev/null | head -20

echo
echo "=== Check for Glance in version catalogs or toml ==="
find . -name "*.toml" | xargs rg -l "glance" 2>/dev/null || true

echo
echo "=== Search project root gradle or settings files ==="
cat -n build.gradle.kts 2>/dev/null | grep -i glance || echo "Not found in build.gradle.kts"
cat -n gradle/libs.versions.toml 2>/dev/null | grep -i glance || echo "Not found in version catalog"

Repository: openMF/mifos-pay

Length of output: 582


🌐 Web query:

Glance 1.1.0 ColorProvider import androidx.glance correct package

💡 Result:

For Glance 1.1.0, the correct import package for the ColorProvider interface is androidx.glance.unit.ColorProvider [1][2]. While you may frequently use ColorProvider within the context of themes or color schemes, the core interface definition resides in the androidx.glance.unit package [2]. It is important to distinguish this from ColorProviders (plural), which is a different class typically found in the androidx.glance.color package and is used for managing sets of color providers for theming [3][4]. Summary of key related packages: - ColorProvider: androidx.glance.unit [1][2] - ColorProviders: androidx.glance.color [3][4] - GlanceTheme: androidx.glance.material or androidx.glance.material3 (depending on your dependency) [5]

Citations:


Remove the incorrect ColorProvider import to fix compilation.

Line 30 imports ColorProvider from androidx.glance.color, but the correct import is from androidx.glance.unit (line 44). The duplicate import from conflicting packages creates an ambiguous symbol that will prevent compilation. Remove the import on line 30.

Proposed fix
-import androidx.glance.color.ColorProvider
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import androidx.glance.color.ColorProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.width
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import org.koin.core.component.KoinComponent
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.width
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import org.koin.core.component.KoinComponent
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/src/main/kotlin/org/mifospay/widget/FinanceGlanceWidget.kt`
around lines 30 - 45, There is a duplicate and conflicting import of
ColorProvider class in the FinanceGlanceWidget.kt file. The import from
androidx.glance.color package (appearing earlier in the imports) is incorrect
and conflicts with the proper import from androidx.glance.unit package. Remove
the ColorProvider import from androidx.glance.color and keep only the
ColorProvider import from androidx.glance.unit to resolve the ambiguous symbol
compilation error.


is DataState.Success -> {
val transactions = result.data
calculateSummary(transactions)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Confirm where calculateSummary is invoked.
rg -n "calculateSummary\\(" feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt -C 3

# 2) Confirm applyFilter does/does not update summary fields.
rg -n "fun applyFilter|totalCredit|totalDebit|viewState = HistoryState.ViewState.Content" \
  feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt -C 4

# 3) Confirm how chart consumes summary fields.
rg -n "MifosPieIncomeExpense|totalCredit|totalDebit" \
  feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryScreen.kt -C 4

Repository: openMF/mifos-pay

Length of output: 3801


Chart totals do not update when filter is applied, causing data mismatch.

calculateSummary() is called only once at fetch time (line 78) with the full transaction list. When applyFilter() is invoked (lines 185-199), it updates the displayed transaction list but does not recalculate or update totalCredit/totalDebit. As a result, the MifosPieIncomeExpense chart (HistoryScreen.kt, lines 141-144) displays totals from all transactions while the list shows only filtered transactions.

Recalculate summary within applyFilter() to reflect the filtered transaction set, or clarify product intent if chart should always show account-level totals regardless of filter (in which case visual separation is needed to avoid user confusion).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/history/src/commonMain/kotlin/org/mifospay/feature/history/HistoryViewModel.kt`
at line 78, The calculateSummary() function in HistoryViewModel is only called
once during initial transaction fetch, so when applyFilter() updates the
filtered transaction list, the totalCredit and totalDebit values are not
recalculated. This causes the chart to display totals from all transactions
while the list shows only filtered ones. Within the applyFilter() method (lines
185-199), after updating the filtered transaction list, add a call to
calculateSummary() passing the filtered transactions to ensure totalCredit and
totalDebit reflect the current filtered data set displayed to the user.

it.copy(dialogState = null)
}

viewModelScope.launch { widgetManagerRepository.refresh() }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard post-transfer widget refresh from crashing the success flow.

At Line 192, viewModelScope.launch { widgetManagerRepository.refresh() } has no failure handling; a refresh exception can crash the VM scope path after a successful transfer.

Suggested fix
-                viewModelScope.launch { widgetManagerRepository.refresh() }
+                viewModelScope.launch {
+                    runCatching { widgetManagerRepository.refresh() }
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModelScope.launch { widgetManagerRepository.refresh() }
viewModelScope.launch {
runCatching { widgetManagerRepository.refresh() }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/make-transfer/src/commonMain/kotlin/org/mifospay/feature/make/transfer/MakeTransferViewModel.kt`
at line 192, The viewModelScope.launch block containing
widgetManagerRepository.refresh() lacks error handling, which means any
exception thrown during the refresh operation can crash the ViewModel scope and
interrupt the successful transfer flow. Wrap the
widgetManagerRepository.refresh() call in a try-catch block to gracefully handle
any exceptions that may occur during the refresh operation, ensuring the success
flow continues uninterrupted even if the widget refresh fails. Optionally log
the exception for debugging purposes while preventing it from propagating to
crash the scope.

Comment on lines +367 to +369
viewModelScope.launch {
widgetManagerRepository.refresh()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Isolate widget-refresh failures from the successful transfer path.

refresh() is a non-critical side effect here. If it throws, the launched coroutine can fail right after a successful transfer state update. Wrap this call defensively so widget sync failure does not affect transfer success UX.

🛡️ Proposed fix
-                viewModelScope.launch {
-                    widgetManagerRepository.refresh()
-                }
+                launchIO {
+                    runCatching { widgetManagerRepository.refresh() }
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModelScope.launch {
widgetManagerRepository.refresh()
}
viewModelScope.launch {
runCatching { widgetManagerRepository.refresh() }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/transfer-interbank/src/commonMain/kotlin/org/mifospay/feature/transfer/interbank/InterbankTransferViewModel.kt`
around lines 367 - 369, The widgetManagerRepository.refresh() call inside the
viewModelScope.launch block can throw an exception that would fail the coroutine
after the transfer has already succeeded, negatively impacting the user
experience. Wrap the widgetManagerRepository.refresh() call in a try-catch block
to defensively handle any exceptions it might throw, ensuring that widget
refresh failures do not interrupt or affect the successful transfer completion
flow.

transferResult = transferResult,
)
}
widgetManagerRepository.refresh()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard widget refresh so transfer success cannot fail post-commit.

This call runs inside the success branch coroutine without error isolation. If refresh throws, it can terminate the flow after transfer success is already committed in UI state.

🛡️ Proposed fix
-                    widgetManagerRepository.refresh()
+                    runCatching { widgetManagerRepository.refresh() }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
widgetManagerRepository.refresh()
runCatching { widgetManagerRepository.refresh() }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/transfer-intrabank/src/commonMain/kotlin/org/mifospay/feature/transfer/intrabank/confirm/TransferConfirmViewModel.kt`
at line 343, The widgetManagerRepository.refresh() call in the success branch of
TransferConfirmViewModel lacks error isolation, which means any exception thrown
during refresh can fail the entire flow even after the transfer has been
successfully committed. Wrap the refresh() call in a try-catch block to catch
and handle any exceptions gracefully without propagating them up the call stack,
ensuring that widget refresh failures do not impact the already-committed
transfer success state.

Comment thread gradle/libs.versions.toml
Comment on lines 143 to +144
[libraries]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate [libraries] table header.

Line 143/Line 144 define [libraries] twice in a row. Duplicate TOML table declarations are invalid and can break version-catalog parsing.

Suggested fix
-[libraries]
 [libraries]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gradle/libs.versions.toml` around lines 143 - 144, The
gradle/libs.versions.toml file contains a duplicate `[libraries]` table header
declaration on consecutive lines, which is invalid TOML syntax and breaks
version-catalog parsing. Remove one of the duplicate `[libraries]` table headers
so that only a single instance remains in the file.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmp-android/dependencies/prodReleaseRuntimeClasspath.txt (1)

229-230: 🩺 Stability & Availability | 🔴 Critical

Verify the version upgrades for androidx.work:work-runtime-ktx and io.insert-koin:koin-androidx-workmanager in gradle.

The lockfile shows:

  • androidx.work:work-runtime-ktx:2.10.3 (line 231)
  • io.insert-koin:koin-androidx-workmanager:4.1.1 (line 382)

However, the gradle file declares:

  • work-runtime-ktx:2.9.1 (line 135)
  • koin-androidx-workmanager:3.5.6 (line 137)

These are transitive dependency upgrades (2.10.3 vs 2.9.1 for work-runtime, 4.1.1 vs 3.5.6 for koin-androidx-workmanager). Confirm whether these upgrades are intentional or if the gradle declarations should be updated to match the resolved versions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.txt` around lines 229 -
230, The gradle dependency declarations for work-runtime-ktx (currently 2.9.1)
and koin-androidx-workmanager (currently 3.5.6) have significant version
upgrades resolved in the lockfile to 2.10.3 and 4.1.1 respectively. Verify
whether these transitive dependency upgrades are intentional by checking your
gradle configuration. If the upgrades are intentional, update the gradle file
declarations to explicitly specify the newer versions for work-runtime-ktx and
koin-androidx-workmanager to match the resolved lockfile versions. If these
upgrades were unintentional, investigate the dependency tree to understand what
is pulling in these newer versions and either lock them to the declared versions
or update the declarations accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.txt`:
- Around line 229-230: The gradle dependency declarations for work-runtime-ktx
(currently 2.9.1) and koin-androidx-workmanager (currently 3.5.6) have
significant version upgrades resolved in the lockfile to 2.10.3 and 4.1.1
respectively. Verify whether these transitive dependency upgrades are
intentional by checking your gradle configuration. If the upgrades are
intentional, update the gradle file declarations to explicitly specify the newer
versions for work-runtime-ktx and koin-androidx-workmanager to match the
resolved lockfile versions. If these upgrades were unintentional, investigate
the dependency tree to understand what is pulling in these newer versions and
either lock them to the declared versions or update the declarations
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cb2fbb6-3040-431d-b783-f70ab7b089d7

📥 Commits

Reviewing files that changed from the base of the PR and between 2526ac4 and 2a88c93.

📒 Files selected for processing (3)
  • cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt
  • cmp-android/dependencies/prodReleaseRuntimeClasspath.txt
  • core/ui/build.gradle.kts
💤 Files with no reviewable changes (1)
  • core/ui/build.gradle.kts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants