Skip to content

Approve button in recurring account Issue#2580

Open
ufuomaisaac wants to merge 37 commits intoopenMF:developmentfrom
ufuomaisaac:issue
Open

Approve button in recurring account Issue#2580
ufuomaisaac wants to merge 37 commits intoopenMF:developmentfrom
ufuomaisaac:issue

Conversation

@ufuomaisaac
Copy link

@ufuomaisaac ufuomaisaac commented Jan 22, 2026

Fixes - Jira-#MIFOSAC-361

Didn't create a Jira ticket, click here to create new.

Please Add Screenshots If there are any UI changes.

Before After

|

trim.050A5CAE-8058-447A-B387-84C25843E0C6.MOV

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Run the static analysis check ./gradlew check or ci-prepush.sh to make sure you didn't break anything

  • If you have multiple commits please combine them into one commit by squashing them.

Summary by CodeRabbit

  • New Features

    • Recurring deposit approval flow: approval screen, navigation, backend approval endpoint, view model and use-case for approving accounts.
  • UI/UX Improvements

    • Date picker, approval-reason input, save/confirm flow, success/error dialogs, lifecycle auto-refresh on resume, and list actions to approve or view accounts.
  • Other

    • Added approval-related UI strings, standardized approval date formatting, and JSON encoding now includes default values.

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a recurring deposit approval feature: new approval model, API/service/datamanager/repository + use-case and ViewModel, navigation and approval UI (with date picker and lifecycle refresh), DateHelper date-format helper, DI wiring, strings, and a Ktor JSON config tweak.

Changes

Cohort / File(s) Summary
Date utility
core/common/src/commonMain/kotlin/com/mifos/core/common/utils/DateHelper.kt
Added apiDateFormat and getDateAsStringForApproval(timeInMillis: Long) for approval-specific date formatting.
Model
core/model/src/commonMain/kotlin/.../recurring/approval/RecurringDepositApproval.kt
Added @Serializable RecurringDepositApproval data class (locale, dateFormat, approvedOnDate?, note?).
Repository & impl
core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt, core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/RecurringAccountRepositoryImp.kt
Added suspend fun approveRecurringDepositAccount(accountId: String, approval: RecurringDepositApproval): GenericResponse to interface and implementation (delegates to datamanager).
Network service & datamanager
core/network/src/commonMain/kotlin/.../RecurringAccountService.kt, core/network/src/commonMain/kotlin/.../DataManagerRecurringAccount.kt
Added POST API endpoint approveRecurringDepositAccount(accountId, approval); datamanager posts payload, throws on non-success, decodes GenericResponse.
Ktor JSON config
core/network/src/androidMain/kotlin/com/mifos/core/network/KtorHttpClient.android.kt
Enabled encodeDefaults = true in Json configuration for ContentNegotiation.
DI & use-case
core/domain/src/commonMain/kotlin/.../UseCaseModule.kt, core/domain/src/commonMain/kotlin/.../useCases/ApproveRecurringDepositUseCase.kt, feature/recurringDeposit/src/commonMain/kotlin/.../RecurringDepositModule.kt
Registered ApproveRecurringDepositUseCase and bound RecurringDepositAccountApprovalViewModel in DI; added domain use-case wrapping repository call into Flow<DataState>.
Navigation
feature/client/.../ClientNavigation.kt, feature/recurringDeposit/.../RecurringDepositAccountApprovalNavigation.kt
Added RecurringDepositAccountApprovalRoute, destination and NavController helper; updated client destinations to accept onApproveAccount / onViewAccount callbacks.
UI, ViewModel & feature
feature/recurringDeposit/.../RecurringDepositAccountApprovalScreen.kt, .../RecurringDepositAccountApprovalViewModel.kt, feature/client/.../RecurringDepositAccountScreen.kt
New approval screen/composables, ViewModel and UI state machine (Initial/Progress/Success/Error); date picker, reason input, save flow, lifecycle-driven refresh, scaffold and dialogs.
Strings
feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml
Added UI strings for approval flow (approve account, select date, approved on, approval reason, save/continue, success/failure titles and messages).

Sequence Diagram(s)

sequenceDiagram
    rect rgba(220,230,241,0.5)
    participant User
    end
    rect rgba(200,240,200,0.5)
    participant Screen
    end
    rect rgba(255,230,200,0.5)
    participant VM
    end
    rect rgba(240,200,255,0.5)
    participant UseCase
    end
    rect rgba(255,220,220,0.5)
    participant Repo
    end
    rect rgba(220,255,240,0.5)
    participant Service
    end
    rect rgba(240,240,220,0.5)
    participant API
    end

    User->>Screen: pick date, enter reason, tap Save
    Screen->>VM: approveRecurringDepositApplication(accountId, approval)
    VM->>VM: emit ShowProgressbar
    VM->>UseCase: invoke(accountId, approval)
    UseCase->>Repo: approveRecurringDepositAccount(accountId, approval)
    Repo->>Service: approveRecurringDepositAccount(accountId, approval)
    Service->>API: POST /recurringdepositaccounts/{accountId}?command=approve
    API-->>Service: HttpResponse
    Service-->>Repo: HttpResponse
    Repo->>Repo: decode -> GenericResponse or throw
    Repo-->>UseCase: DataState.Success / DataState.Error
    UseCase-->>VM: DataState
    VM-->>Screen: update UI state (success / error)
    Screen->>User: show snackbar/dialog and navigate back on success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • biplab1
  • itsPronay
  • revanthkumarJ

Poem

🐰 I hopped in with a tiny grin,
Picked a date and scribbled a note,
Sent it down the VM-and-repo moat,
"Approved!" they cheered — I twitched my chin,
I nibbled a carrot, then hopped home.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Approve button in recurring account Issue' refers to the approval flow feature added in this PR, but is vague and grammatically awkward (missing article or punctuation). Revise the title to be more descriptive and grammatically correct, e.g., 'Add approval flow for recurring deposit accounts' or 'Implement recurring deposit account approval feature'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@core/common/src/commonMain/kotlin/com/mifos/core/common/utils/DateHelper.kt`:
- Around line 44-50: The apiDateFormat constant currently uses day() which
produces non-padded days; replace that call with dayOfMonth(Padding.ZERO) in the
apiDateFormat formatter (the LocalDateTime.Format block) so dates are rendered
as "dd MMMM yyyy" (e.g., "05 January 2026") to match the API and the other date
format constants referenced by RecurringDepositAccountApprovalScreen.kt:223.

In
`@core/network/src/androidMain/kotlin/com/mifos/core/network/KtorHttpClient.android.kt`:
- Around line 31-41: The file currently installs ContentNegotiation twice which
causes DuplicatePluginException; remove the redundant
install(ContentNegotiation) block and keep a single installation that preserves
the desired Json settings (ensure ignoreUnknownKeys = true, encodeDefaults =
true, isLenient = true, prettyPrint = true, explicitNulls = false) so the final
install(ContentNegotiation) { json(Json { ... }) } contains all required
options; verify only one call to install(ContentNegotiation) exists and run a
quick build to confirm HttpClient initializes without the duplicate plugin
error.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt`:
- Around line 254-265: clientRecurringDepositAccountDestination is passing
onViewAccount as an empty lambda and onApproveAccount forwards accountNumber to
navigateToRecurringDepositAccountApproval which expects accountId; wire the
onViewAccount callback to navigate to the appropriate account view (e.g., call
navController.navigateToRecurringDepositAccount with the same identifier) and
make identifier names consistent: either rename accountNumber to accountId in
the onApproveAccount lambda or adapt the
navigateToRecurringDepositAccountApproval invocation to accept accountNumber,
ensuring RecurringDepositAccountScreen's OnViewAccount event and
navigateToRecurringDepositAccountApproval use the same identifier name/type.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt`:
- Line 178: Fix the typo in the TODO comment inside
RecurringDepositAccountScreen (RecurringDepositAccountScreen.kt): replace
"clientimplement" with either "client implement" or simply "implement" (e.g.,
"todo implement search bar functionality") so the TODO reads clearly and
consistently with other comments.
- Around line 76-78: The LaunchedEffect keyed on
navController.currentBackStackEntry causes a duplicate fetch because the
ViewModel already loads data in its init via
checkNetworkAndRecurringDepositAccounts(); remove or change this LaunchedEffect
to avoid the immediate re-fetch: either delete the LaunchedEffect that calls
viewModel.trySendAction(RecurringDepositAccountAction.Refresh) or replace it
with a targeted mechanism that only triggers on return (e.g., observe
NavBackStackEntry.lifecycle events or a navigation result/SharedViewModel flag)
so that refresh runs only when coming back from another screen rather than on
initial composition.

In
`@feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt`:
- Around line 113-121: Replace the hardcoded dialogTitle "OK" in the
RecurringDepositAccountApprovalUiState.ShowRecurringDepositAccountApprovedSuccessfully
branch with a localized string resource; update the MifosAlertDialog call to use
stringResource(Res.string.xxx) (e.g., Res.string.ok or a feature-specific
resource) for dialogTitle so the title is internationalized while keeping
onConfirmation calling navigateBack and other parameters unchanged.
- Line 235: The file defines a local no-op function rememberDatePickerState that
shadows the imported API and causes datePickerState to be Unit; remove this
stubbed function (the rememberDatePickerState declaration) so the code uses the
correct imported rememberDatePickerState and datePickerState will have the
proper type to access selectedDateMillis, or if you intentionally need a local
helper, rename it and match the real API signature to return the proper state
instead of Unit.
- Around line 142-147: The date picker currently uses
SelectableDates.isSelectableDate to allow only dates >= Clock.System.now(),
blocking today and past dates; update the logic in the
datePickerState/selectableDates block (SelectableDates.isSelectableDate) to
allow today and earlier by inverting the comparison (use utcTimeMillis <=
Clock.System.now().toEpochMilliseconds()) or, better, compare dates by LocalDate
(convert utcTimeMillis and now to LocalDate and allow selectable when
candidateDate <= today) so same-day approvals are permitted; adjust the
implementation in RecurringDepositAccountApprovalScreen where datePickerState is
created.
🧹 Nitpick comments (5)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt (1)

232-250: Empty string fallback for null accountNo may cause silent failures.

Passing an empty string when accountNo is null (lines 237, 244) could lead to confusing behavior or failed API calls downstream. Consider either:

  1. Filtering out items with null accountNo from displaying actions, or
  2. Handling the null case explicitly (e.g., showing an error dialog)
Suggested approach: guard against null before dispatching action
                                        onActionClicked = { actions ->
+                                           val accountNo = recurringDeposit.accountNo
+                                           if (accountNo.isNullOrEmpty()) return@MifosActionsSavingsListingComponent
                                            when (actions) {
                                                is Actions.ViewAccount -> {
                                                    onAction(
                                                        RecurringDepositAccountAction.ViewAccount(
-                                                            recurringDeposit.accountNo ?: "",
+                                                            accountNo,
                                                        )
                                                    )
                                                }
                                                is Actions.ApproveAccount -> {
                                                    onAction(
                                                        RecurringDepositAccountAction.ApproveAccount(
-                                                            recurringDeposit.accountNo ?: "",
+                                                            accountNo,
                                                        )
                                                    )
                                                }
                                                else -> Unit
                                            }
                                        }
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/di/RecurringDepositModule.kt (1)

22-33: Consider using viewModelOf for consistency.

The existing RecurringAccountViewModel uses viewModelOf(::RecurringAccountViewModel) (line 20), while the new RecurringDepositAccountApprovalViewModel uses explicit parameter injection. If SavedStateHandle and ApproveRecurringDepositUseCase are both available via DI, you could simplify:

♻️ Suggested simplification
-    viewModel {
-        RecurringDepositAccountApprovalViewModel(
-            savedStateHandle = get(),
-            approveRecurringDepositUseCase = get()
-        )
-    }
+    viewModelOf(::RecurringDepositAccountApprovalViewModel)

-    factory {
-        ApproveRecurringDepositUseCase(
-            repository = get()
-        )
-    }
+    factory(::ApproveRecurringDepositUseCase)
core/network/src/commonMain/kotlin/com/mifos/core/network/services/RecurringAccountService.kt (1)

37-41: Use APIEndPoint.CREATE_RECURRING_DEPOSIT_ACCOUNTS for consistency.

Lines 26 and 31 use APIEndPoint.CREATE_RECURRING_DEPOSIT_ACCOUNTS, but this endpoint uses a hardcoded string. For maintainability and consistency with the rest of the interface, apply the suggested refactor:

♻️ Suggested refactor
-    `@POST`("recurringdepositaccounts/{accountId}?command=approve")
+    `@POST`(APIEndPoint.CREATE_RECURRING_DEPOSIT_ACCOUNTS + "/{accountId}?command=approve")
     fun approveRecurringDepositAccount(
         `@Path`("accountId") accountId: String,
         `@Body` approval: RecurringDepositApproval,
     ): Flow<HttpResponse>
core/model/src/commonMain/kotlin/com/mifos/core/model/objects/template/recurring/approval/RecurringDepositApproval.kt (1)

15-22: Prefer immutable properties for request payloads.
These fields look write-once; switching to val avoids accidental mutation.

♻️ Suggested change
-data class RecurringDepositApproval(
-    var locale: String = "en",
-    var dateFormat: String = "dd MMMM yyyy",
-    var approvedOnDate: String? = null,
-    var note: String? = null,
-)
+data class RecurringDepositApproval(
+    val locale: String = "en",
+    val dateFormat: String = "dd MMMM yyyy",
+    val approvedOnDate: String? = null,
+    val note: String? = null,
+)
core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt (1)

56-71: Consider extracting the shared response-mapping logic.
This duplicates the error/success parsing in createRecurringDepositAccount; a helper keeps it consistent and easier to maintain.

Comment on lines 31 to 41
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
encodeDefaults = true
isLenient = true
prettyPrint = true
explicitNulls = false
},
)
}
Copy link

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

🏁 Script executed:

fd -t f "KtorHttpClient.android.kt" | head -5

Repository: openMF/android-client

Length of output: 243


🏁 Script executed:

cat -n core/network/src/androidMain/kotlin/com/mifos/core/network/KtorHttpClient.android.kt | head -80

Repository: openMF/android-client

Length of output: 2500


🌐 Web query:

Ktor HttpClient ContentNegotiation install twice duplicate plugin exception

💡 Result:

Cause

  • The exception means ContentNegotiation was installed more than once with the same plugin key (e.g., installed twice on the same HttpClient or both globally and again in a test/server scope). Ktor throws DuplicatePluginException when a plugin is installed twice. [1][2]

How to fix

  • Ensure you install ContentNegotiation only once per client (or per application scope). Remove the duplicate install (common places: shared client factory, tests that call module twice, or installing on both a parent route and its child incorrectly). [1][3]

How to check/guard in code (client)

  • Use pluginOrNull to see if the plugin is already installed before installing:
    • if (client.pluginOrNull(ContentNegotiation) == null) { /* install */ }
    • or when creating clients, centralize installation in one factory so callers reuse the same configured client. [2]

Example (HttpClient factory)

  • val client = HttpClient(CIO) {
    install(ContentNegotiation) { json() }
    }

Testing tip

  • If tests start your app twice (EngineMain vs application { module(...) } or you call module twice), remove the duplicate startup or use embeddedServer only in one place to avoid reinstalling server plugins. StackOverflow examples show this pattern as a common cause. [3]

Sources

  • Ktor Client ContentNegotiation docs. [1]
  • Ktor API: pluginOrNull for HttpClient. [2]
  • StackOverflow discussion about DuplicatePluginException when modules/tests start things twice. [3]

Consolidate duplicate ContentNegotiation installations (causes DuplicatePluginException).

This file installs ContentNegotiation twice (lines 31–41 and lines 53–62). Ktor throws DuplicatePluginException when the same plugin is installed more than once, preventing HttpClient initialization. Keep one install(ContentNegotiation) block with your desired JSON configuration. Note: the first block includes encodeDefaults = true, which the second lacks—ensure you retain the options you need.

🤖 Prompt for AI Agents
In
`@core/network/src/androidMain/kotlin/com/mifos/core/network/KtorHttpClient.android.kt`
around lines 31 - 41, The file currently installs ContentNegotiation twice which
causes DuplicatePluginException; remove the redundant
install(ContentNegotiation) block and keep a single installation that preserves
the desired Json settings (ensure ignoreUnknownKeys = true, encodeDefaults =
true, isLenient = true, prettyPrint = true, explicitNulls = false) so the final
install(ContentNegotiation) { json(Json { ... }) } contains all required
options; verify only one call to install(ContentNegotiation) exists and run a
quick build to confirm HttpClient initializes without the duplicate plugin
error.

Comment on lines 254 to 265
clientRecurringDepositAccountDestination(
navController = navController,
navigateBack = navController::popBackStack,
{},
{},
onApproveAccount = { accountNumber ->
navController.navigateToRecurringDepositAccountApproval(accountNumber)
},
onViewAccount = {},
)

recurringDepositAccountApprovalDestination(
navigateBack = navController::popBackStack,
)
Copy link

@coderabbitai coderabbitai bot Jan 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for clientRecurringDepositAccountDestination function definition
rg -n "fun.*clientRecurringDepositAccountDestination" --type=kt -A 10

Repository: openMF/android-client

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Search for recurringDepositAccountApprovalDestination and how it uses parameters
rg -n "fun.*recurringDepositAccountApprovalDestination" --type=kt -A 10

Repository: openMF/android-client

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Search for how onApproveAccount callback is defined in the recurring deposit screen
rg -n "onApproveAccount" --type=kt -B 3 -A 3

Repository: openMF/android-client

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for how onViewAccount callback is defined/used
rg -n "onViewAccount" --type=kt -B 3 -A 3

Repository: openMF/android-client

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Check the navigateToRecurringDepositAccountApproval extension function
rg -n "navigateToRecurringDepositAccountApproval" --type=kt -A 5

Repository: openMF/android-client

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for clientRecurringDepositAccountDestination function definition
rg -n "fun.*clientRecurringDepositAccountDestination" -A 10

Repository: openMF/android-client

Length of output: 1805


🏁 Script executed:

#!/bin/bash
# Search for recurringDepositAccountApprovalDestination definition
rg -n "fun.*recurringDepositAccountApprovalDestination" -A 10

Repository: openMF/android-client

Length of output: 2090


🏁 Script executed:

#!/bin/bash
# Search for onApproveAccount usage patterns
rg -n "onApproveAccount" -B 2 -A 2

Repository: openMF/android-client

Length of output: 7632


🏁 Script executed:

#!/bin/bash
# Search for onViewAccount usage patterns
rg -n "onViewAccount" -B 2 -A 2

Repository: openMF/android-client

Length of output: 7593


🏁 Script executed:

#!/bin/bash
# Search for navigateToRecurringDepositAccountApproval extension
rg -n "navigateToRecurringDepositAccountApproval" -A 5

Repository: openMF/android-client

Length of output: 2888


Wire the onViewAccount callback and verify the account identifier naming.

The onViewAccount callback is actively used by RecurringDepositAccountScreen (triggered on OnViewAccount event), but it's passed as an empty lambda in the navigation setup. Additionally, the onApproveAccount callback passes accountNumber to navigateToRecurringDepositAccountApproval(), which expects a parameter named accountId—verify these represent the same identifier and use consistent naming throughout.

🤖 Prompt for AI Agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt`
around lines 254 - 265, clientRecurringDepositAccountDestination is passing
onViewAccount as an empty lambda and onApproveAccount forwards accountNumber to
navigateToRecurringDepositAccountApproval which expects accountId; wire the
onViewAccount callback to navigate to the appropriate account view (e.g., call
navController.navigateToRecurringDepositAccount with the same identifier) and
make identifier names consistent: either rename accountNumber to accountId in
the onApproveAccount lambda or adapt the
navigateToRecurringDepositAccountApproval invocation to accept accountNumber,
ensuring RecurringDepositAccountScreen's OnViewAccount event and
navigateToRecurringDepositAccountApproval use the same identifier name/type.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ufuomaisaac Please address this issue.

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@biplab1
Copy link
Contributor

biplab1 commented Jan 23, 2026

@ufuomaisaac Please address the @coderabbitai reviews which are relevant to this PR. Make sure the UI guidelines are being followed as well.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# List all review comments made by CodeRabbit on this PR
gh pr view 2580 --json reviews,comments --jq '.reviews[] | select(.author.login == "coderabbitai") | {state: .state, body: .body, submittedAt: .submittedAt}' 2>/dev/null || echo "No reviews found or error accessing reviews"

# Also get review comments (line-level comments)
gh api "repos/openMF/android-client/pulls/2580/comments" --jq '.[] | select(.user.login == "coderabbitai") | {path: .path, line: .line, body: .body, created_at: .created_at}' 2>/dev/null || echo "No review comments found"

Length of output: 11400


@biplab1 @ufuomaisaac, I found my review from January 23rd with several items that need attention. Here's a summary:

Critical Issues (Must Fix) 🔴

  1. Duplicate Plugin Installation (KtorHttpClient.android.kt):

    • ContentNegotiation is installed twice, causing DuplicatePluginException
    • Remove one of the duplicate blocks and keep a single installation
  2. Date Picker State Issue (RecurringDepositAccountApprovalScreen.kt, Line 235):

    • Local stub function rememberDatePickerState shadows the real API
    • Remove the stub so the actual date picker works correctly
  3. Navigation Wiring (ClientNavigation.kt, Lines 254-265):

    • onViewAccount callback is empty
    • Identifier inconsistency: accountNumber vs accountId
    • Wire the view account navigation properly

Important Fixes 🟡

  1. Date Format Padding (DateHelper.kt, Lines 44-50):

    • Use dayOfMonth(Padding.ZERO) instead of day() for proper "dd MMMM yyyy" format
  2. Date Picker Logic (RecurringDepositAccountApprovalScreen.kt, Lines 142-147):

    • Currently blocks today and past dates; should allow them for approvals
  3. Duplicate Fetch (RecurringDepositAccountScreen.kt, Lines 76-78):

    • LaunchedEffect causes unnecessary re-fetch on initial load
  4. Hardcoded Dialog Title (RecurringDepositAccountApprovalScreen.kt, Lines 113-121):

    • Use string resource instead of hardcoded "OK"
  5. Typo (RecurringDepositAccountScreen.kt, Line 178):

    • Fix "clientimplement" → "implement"

Code Quality Improvements (Nice to Have) 💡

  • Handle null accountNo gracefully instead of empty string fallback
  • Use viewModelOf for consistency in DI
  • Use APIEndPoint constant instead of hardcoded URL
  • Make properties immutable (val vs var)
  • Extract shared response-mapping logic

I'm happy to help clarify any of these items or provide more specific guidance on how to fix them. Just let me know! 🤖

@ufuomaisaac
Copy link
Author

ufuomaisaac commented Jan 24, 2026

@ufuomaisaac Please address the @coderabbitai reviews which are relevant to this PR. Make sure the UI guidelines are being followed as well.

Alright, will work on that. Thank you!

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Tip

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

@ufuomaisaac, sounds good! I'm here if you need any help or clarification on any of the review items as you work through them. Feel free to ping me if you have questions! 👍

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Tip

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

@ufuomaisaac, sounds good! I'm here if you need any help or clarification on any of the review items as you work through them. Feel free to ping me if you have questions! 👍

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt`:
- Around line 232-249: The code currently passes recurringDeposit.accountNo ?:
"" into RecurringDepositAccountAction.ViewAccount and ApproveAccount; instead of
supplying an empty string, first guard for null by reading val accountNo =
recurringDeposit.accountNo and if accountNo is null skip the action (return from
the onActionClicked branch) or surface an error (call the existing error action
handler if available), otherwise call
onAction(RecurringDepositAccountAction.ViewAccount(accountNo)) /
onAction(RecurringDepositAccountAction.ApproveAccount(accountNo)). Update the
onActionClicked lambda handling for Actions.ViewAccount and
Actions.ApproveAccount to use this null check and avoid passing empty
identifiers.

@ufuomaisaac
Copy link
Author

@biplab1, I have resolved all the issues raised by coderabbitai

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt`:
- Around line 78-93: The lifecycle observer currently captures a stale snapshot
of state.recurringDepositAccounts because DisposableEffect is keyed only on
lifecycleOwner; update the observer to read the latest value by using
rememberUpdatedState for the relevant state (e.g., val currentAccounts by
rememberUpdatedState(state.recurringDepositAccounts)) and reference
currentAccounts inside the LifecycleEventObserver callback, keeping the
DisposableEffect keyed on lifecycleOwner and still adding/removing the observer;
ensure viewModel.trySendAction(RecurringDepositAccountAction.Refresh) is invoked
when event == Lifecycle.Event.ON_RESUME and currentAccounts.isNotEmpty().
🧹 Nitpick comments (1)
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt (1)

237-253: Consider narrowing UI-state visibility.

These are internal UI details; marking them internal avoids leaking API surface outside the feature module.

♻️ Suggested change
-sealed class RecurringDepositAccountApprovalUiState {
+internal sealed class RecurringDepositAccountApprovalUiState {
@@
-class RecurringDepositAccountApprovalScreenPreviewProvider :
+internal class RecurringDepositAccountApprovalScreenPreviewProvider :

@ufuomaisaac
Copy link
Author

@biplab1 I have resolved all the issues that were raised by CodeRabbitAi

@itsPronay
Copy link
Contributor

@ufuomaisaac, please resolve conflicts and request the team for review.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt (1)

304-320: ⚠️ Potential issue | 🟡 Minor

Add content descriptions for accessibility.

The search and filter icons have contentDescription = null, which makes them inaccessible to screen readers. Provide meaningful descriptions for users relying on assistive technologies.

♿ Suggested fix
         Icon(
             painter = painterResource(Res.drawable.search),
-            contentDescription = null,
+            contentDescription = stringResource(Res.string.search),
             modifier = Modifier.clickable {
                 onToggleSearch.invoke()
             },
         )

         Spacer(modifier = Modifier.width(DesignToken.spacing.largeIncreased))

         Icon(
             painter = painterResource(Res.drawable.filter),
-            contentDescription = null,
+            contentDescription = stringResource(Res.string.filter),
             modifier = Modifier.clickable {
                 onToggleFilter.invoke()
             },
         )
🤖 Fix all issues with AI agents
In
`@feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt`:
- Around line 255-268: The save Button currently allows multiple taps because
there's no loading guard; update the RecurringDepositAccountApprovalContent
signature to accept an isLoading: Boolean = false and inside that composable
disable the Button (use enabled = !isLoading or similar) and prevent calling
approveAccount when isLoading is true; then, where you handle
RecurringDepositAccountApprovalUiState switch, pass isLoading = true when state
== RecurringDepositAccountApprovalUiState.ShowProgressbar and use the
default/false for Initial so the Button is re-enabled; reference symbols:
approveAccount, RecurringDepositApproval,
RecurringDepositAccountApprovalContent, and
RecurringDepositAccountApprovalUiState.ShowProgressbar.
- Around line 260-267: The RecurringDepositApproval payload is using a hardcoded
locale "en" in RecurringDepositAccountApprovalScreen where
approveAccount.invoke(...) is called; replace that literal with the device
default language (e.g., Locale.getDefault().language) in the
RecurringDepositApproval constructor (keep dateFormat and approvedOnDate as-is
using DateHelper.getDateAsStringForApproval(approvalDate) and note =
reasonForApproval). If Locale.getDefault() is not available in commonMain,
implement an expect/actual platform accessor (e.g., getDeviceLanguage()) and use
that instead; apply the same replacement pattern to other payload constructors
across the codebase where "en" is hardcoded.
🧹 Nitpick comments (6)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt (3)

128-145: Use string resource for "Error" title and simplify the else branch.

The hardcoded "Error" string should use a string resource for localization consistency. Additionally, else -> null is unnecessary if DialogState is a sealed class with only Error and None states—consider removing it or using else -> Unit.

♻️ Suggested fix
             AlertDialog(
-                title = { Text("Error") },
+                title = { Text(stringResource(Res.string.feature_client_dialog_error_title)) },
                 text = { Text(text = state.dialogState.message) },
                 ...
             )
         }

-        else -> null
+        else -> Unit
     }

You'll need to add a string resource entry for the error title (e.g., feature_client_dialog_error_title).


216-222: Prefer let block over non-null assertion for cleaner null handling.

While the !! is safe here due to the preceding null check, using a let block or smart cast is more idiomatic and avoids the non-null assertion operator.

♻️ Suggested fix
                                 lastActive = if (recurringDeposit.status?.submittedAndPendingApproval == true) {
                                     stringResource(Res.string.client_savings_pending_approval)
-                                } else if (recurringDeposit.lastActiveTransactionDate != null) {
-                                    DateHelper.getDateAsString(recurringDeposit.lastActiveTransactionDate!!)
-                                } else {
-                                    notAvailableText
-                                },
+                                } else {
+                                    recurringDeposit.lastActiveTransactionDate?.let {
+                                        DateHelper.getDateAsString(it)
+                                    } ?: notAvailableText
+                                },

296-299: Use parameterized string resource for better localization.

String concatenation totalItem + " " + stringResource(...) doesn't handle languages with different word orders. Use a parameterized string resource for proper i18n support.

🌐 Suggested approach

Define a string resource with a placeholder:

<string name="client_savings_item_count">%1$s items</string>

Then use:

Text(
    text = stringResource(Res.string.client_savings_item_count, totalItem),
    style = MifosTypography.labelMedium,
)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt (1)

275-280: Empty callbacks for fixed deposit account actions.

Both onApproveAccount and onViewAccount are passed as empty lambdas for the fixed deposit account destination. While this is consistent with the PR's focus on recurring deposits, if the UI exposes approve/view buttons that invoke these callbacks, users will experience no response.

Consider either:

  1. Wiring these callbacks when the corresponding screens are available, or
  2. Adding TODO comments to track the pending implementation.
feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt (2)

99-101: Unused SnackbarHostState.

The snackbar host state is created and passed to MifosScaffold but never used to show messages. Error states use MifosSweetError instead. Consider removing this if snackbar functionality isn't planned, or use it for transient feedback.


279-279: Unused GenericResponse in success state.

The response parameter is stored but never read in the UI—the success dialog shows static localized strings. If the response data isn't needed, this could be simplified to a data object.

♻️ Suggested simplification
-    data class ShowRecurringDepositAccountApprovedSuccessfully(val response: GenericResponse) : RecurringDepositAccountApprovalUiState()
+    data object ShowRecurringDepositAccountApprovedSuccessfully : RecurringDepositAccountApprovalUiState()

If you plan to display response details (e.g., transaction ID) in the future, keep the current structure.

Comment on lines +260 to +267
approveAccount.invoke(
RecurringDepositApproval(
locale = "en",
dateFormat = "dd MMMM yyyy",
approvedOnDate = DateHelper.getDateAsStringForApproval(approvalDate),
note = reasonForApproval,
),
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how locale is handled elsewhere in the codebase
rg -n "locale\s*=\s*\"en\"" --type kotlin -C 2 | head -100

Repository: openMF/android-client

Length of output: 13098


🏁 Script executed:

#!/bin/bash
# Search for locale-related code patterns and utilities
rg -n "Locale\." --type kotlin | head -50

Repository: openMF/android-client

Length of output: 3017


🏁 Script executed:

#!/bin/bash
# Check if there's a locale utility or provider in the project
fd -t f -n "Locale\|locale" --type f | grep -E "\.kt$" | head -30

Repository: openMF/android-client

Length of output: 295


🏁 Script executed:

#!/bin/bash
# Look at the file structure to understand Kotlin Multiplatform setup
ls -la feature/recurringDeposit/src/ 2>/dev/null | head -20

Repository: openMF/android-client

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Check the SettingsScreen locale comment
sed -n '240,260p' feature/settings/src/commonMain/kotlin/com/mifos/feature/settings/settings/SettingsScreen.kt

Repository: openMF/android-client

Length of output: 737


🏁 Script executed:

#!/bin/bash
# Check LanguageHelper.kt for locale management
cat core/common/src/commonMain/kotlin/com/mifos/core/common/utils/LanguageHelper.kt

Repository: openMF/android-client

Length of output: 3210


🏁 Script executed:

#!/bin/bash
# Search for any locale provider or utility function
rg -n "fun.*[Ll]ocale\|object.*Locale\|class.*Locale" --type kotlin -A 3 | head -50

Repository: openMF/android-client

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check if there's a locale manager or preference
rg -n "preferences\|Preferences\|DataStore" --type kotlin | grep -i locale | head -20

Repository: openMF/android-client

Length of output: 47


Use device locale instead of hardcoded "en" for API requests.

The locale is hardcoded to "en" in 14+ locations across the codebase, which means non-English users will receive English-formatted API responses regardless of their device locale settings. This undermines any i18n efforts and breaks API contract assumptions.

Replace hardcoded locale with the device's default language:

RecurringDepositApproval(
    locale = Locale.getDefault().language,
    dateFormat = "dd MMMM yyyy",
    approvedOnDate = DateHelper.getDateAsStringForApproval(approvalDate),
    note = reasonForApproval,
)

For Kotlin Multiplatform projects, consider using expect/actual to abstract platform-specific locale retrieval if Locale.getDefault() is unavailable in commonMain.

This is a systemic issue affecting offline sync, savings, loans, groups, centers, and other features. Standardize on device locale across all API payload constructors.

🤖 Prompt for AI Agents
In
`@feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt`
around lines 260 - 267, The RecurringDepositApproval payload is using a
hardcoded locale "en" in RecurringDepositAccountApprovalScreen where
approveAccount.invoke(...) is called; replace that literal with the device
default language (e.g., Locale.getDefault().language) in the
RecurringDepositApproval constructor (keep dateFormat and approvedOnDate as-is
using DateHelper.getDateAsStringForApproval(approvalDate) and note =
reasonForApproval). If Locale.getDefault() is not available in commonMain,
implement an expect/actual platform accessor (e.g., getDeviceLanguage()) and use
that instead; apply the same replacement pattern to other payload constructors
across the codebase where "en" is hardcoded.

@techsavvy185
Copy link
Contributor

I've fetched the changes and tested locally. Looks good to me!

Comment on lines 21 to 27

viewModel {
RecurringDepositAccountApprovalViewModel(
savedStateHandle = get(),
approveRecurringDepositUseCase = get(),
)
}
Copy link
Contributor

@itsPronay itsPronay Feb 5, 2026

Choose a reason for hiding this comment

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

Don't provide viewmodel like this, see above for reference

Comment on lines 29 to 32
factory {
ApproveRecurringDepositUseCase(
repository = get(),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

RecurringDepositModule is for viewmodel only.
Use cases should not be here; see other use cases and where we provided them

@itsPronay
Copy link
Contributor

@ufuomaisaac, I see we have a new screen addition in your PR, we didn't have that screen before?

@ufuomaisaac
Copy link
Author

ufuomaisaac commented Feb 5, 2026

No, the screen wasn't there before @itsPronay

Thank you for the review. I will make changes and update you.

@ufuomaisaac
Copy link
Author

Thank you for the review! @techsavvy185

@ufuomaisaac ufuomaisaac requested a review from itsPronay February 6, 2026 17:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@core/network/src/commonMain/kotlin/com/mifos/core/network/services/RecurringAccountService.kt`:
- Around line 37-41: The POST mapping in approveRecurringDepositAccount uses a
hardcoded path string; replace it with the APIEndPoint constant to match other
methods. Update the Retrofit annotation on approveRecurringDepositAccount to
reference APIEndPoint.CREATE_RECURRING_DEPOSIT_ACCOUNTS (and append the query
?command=approve as needed) so the service uses the shared endpoint constant
instead of the literal "recurringdepositaccounts/{accountId}" string.
🧹 Nitpick comments (2)
core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt (1)

56-71: Extract a shared Json instance to avoid repeated allocation.

Json { ignoreUnknownKeys = true } is instantiated on every call in both createRecurringDepositAccount (line 40) and here (line 69). Allocating a Json instance is not free — the builder configures a serializers module each time. Hoist it to a class-level val or companion object.

♻️ Proposed refactor

Add a companion (or private val) at the top of the class and reuse it:

 class DataManagerRecurringAccount(
     val mBaseApiManager: BaseApiManager,
 ) {
+    private val json = Json { ignoreUnknownKeys = true }
+
     fun createRecurringDepositAccount(
         ...
     ): Flow<GenericResponse> {
         ...
-            val json = Json { ignoreUnknownKeys = true }
-
-            json.decodeFromString<GenericResponse>(response.bodyAsText())
+            this@DataManagerRecurringAccount.json.decodeFromString<GenericResponse>(response.bodyAsText())
         }
     }
     ...
     suspend fun approveRecurringDepositAccount(
         ...
     ): GenericResponse {
         ...
-        val json = Json { ignoreUnknownKeys = true }
-        return json.decodeFromString<GenericResponse>(response.bodyAsText())
+        return json.decodeFromString<GenericResponse>(response.bodyAsText())
     }
 }
core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt (1)

30-33: Inconsistent return-type convention within the repository interface.

The existing methods return Flow<DataState<...>>, while this new method is a raw suspend fun returning GenericResponse. The Flow/DataState wrapping is deferred to the use-case layer instead. This works, but future contributors may find the mixed contract confusing.

Consider either aligning this method with the existing Flow<DataState<...>> pattern, or leaving a brief KDoc comment on the method explaining why it differs.

@ufuomaisaac
Copy link
Author

@itsPronay, I have made all the requested changes. Please take a look at it when you have the time.

Comment on lines 254 to 265
clientRecurringDepositAccountDestination(
navController = navController,
navigateBack = navController::popBackStack,
{},
{},
onApproveAccount = { accountNumber ->
navController.navigateToRecurringDepositAccountApproval(accountNumber)
},
onViewAccount = {},
)

recurringDepositAccountApprovalDestination(
navigateBack = navController::popBackStack,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

@ufuomaisaac Please address this issue.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt`:
- Around line 157-163: The Column inside the MifosScaffold is ignoring the
scaffold's paddingValues; update the Column's modifier in
RecurringDepositAccountScreen so it consumes those insets (use
Modifier.fillMaxSize().padding(paddingValues) or apply the appropriate padding
values via paddingValues.calculateTopPadding()/calculateBottomPadding() as
needed) so content won't render under system/top/bottom bars; locate the
MifosScaffold usage and the Column declaration to apply the padding.

In
`@feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt`:
- Around line 114-129: The content's isLoading prop isn't being driven by the
parent state so the double-tap guard is ineffective; in
RecurringDepositAccountApprovalScreen, when building the UI for non-error
states, call RecurringDepositAccountApprovalContent with isLoading = (uiState is
RecurringDepositAccountApprovalUiState.ShowProgressbar) (and continue to pass
approveAccount), and if you still want a visible spinner show
MifosProgressIndicator as an overlay or conditional child while keeping the
content rendered with isLoading=true so the Save button (in
RecurringDepositAccountApprovalContent) is immediately disabled.
🧹 Nitpick comments (5)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/recurringDepositAccount/RecurringDepositAccountScreen.kt (2)

140-145: else -> null is a no-op in a statement-position when.

This branch silently evaluates to null which is discarded. Use else -> Unit (consistent with the pattern at line 258) or omit the branch if the when is already exhaustive.

Proposed fix
-        else -> null
+        else -> Unit

212-218: Consider replacing the !! with a safe call.

Line 215 uses a non-null assertion on lastActiveTransactionDate right after a null check. While safe at runtime, a let block is more idiomatic and resilient to future refactoring.

Proposed fix
-                                        lastActive = if (recurringDeposit.status?.submittedAndPendingApproval == true) {
-                                            stringResource(Res.string.client_savings_pending_approval)
-                                        } else if (recurringDeposit.lastActiveTransactionDate != null) {
-                                            DateHelper.getDateAsString(recurringDeposit.lastActiveTransactionDate!!)
-                                        } else {
-                                            notAvailableText
-                                        },
+                                        lastActive = when {
+                                            recurringDeposit.status?.submittedAndPendingApproval == true ->
+                                                stringResource(Res.string.client_savings_pending_approval)
+                                            recurringDeposit.lastActiveTransactionDate != null ->
+                                                DateHelper.getDateAsString(recurringDeposit.lastActiveTransactionDate)
+                                            else -> notAvailableText
+                                        },

Note: If lastActiveTransactionDate is typed as nullable, the Kotlin smart-cast inside a when branch after the != null check will handle it without !!.

feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalViewModel.kt (2)

45-76: Redundant eager progress state update on line 46.

ShowProgressbar is set on line 46 before the coroutine collects, and then again on line 60 when DataState.Loading is emitted. The eager set on line 46 is fine for instant UI feedback, but the DataState.Loading branch (lines 59-63) is then redundant. Consider removing one or the other for clarity.


35-37: approveRecurringDepositApplication is unused — remove or use as screen callback.

The function is never called anywhere in the codebase. The screen directly invokes viewModel.trySendAction(RecurringDepositAccountApprovalAction.Approve(it)) instead, bypassing this wrapper method. Either remove the function or use it as the screen's callback to eliminate dead code.

feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/recurringAccountApproval/RecurringDepositAccountApprovalScreen.kt (1)

274-279: Consider using sealed interface instead of sealed class.

The UI state has no shared mutable state or constructor logic, so sealed interface would be more idiomatic and consistent with the RecurringDepositAccountApprovalAction definition in the ViewModel file (which already uses sealed interface).

Proposed change
-sealed class RecurringDepositAccountApprovalUiState {
-    data object Initial : RecurringDepositAccountApprovalUiState()
-    data object ShowProgressbar : RecurringDepositAccountApprovalUiState()
-    data class ShowRecurringDepositAccountApprovedSuccessfully(val response: GenericResponse) : RecurringDepositAccountApprovalUiState()
-    data class ShowError(val message: String) : RecurringDepositAccountApprovalUiState()
-}
+sealed interface RecurringDepositAccountApprovalUiState {
+    data object Initial : RecurringDepositAccountApprovalUiState
+    data object ShowProgressbar : RecurringDepositAccountApprovalUiState
+    data class ShowRecurringDepositAccountApprovedSuccessfully(val response: GenericResponse) : RecurringDepositAccountApprovalUiState
+    data class ShowError(val message: String) : RecurringDepositAccountApprovalUiState
+}

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 9, 2026

@ufuomaisaac ufuomaisaac requested a review from biplab1 February 11, 2026 07:54
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.

4 participants