feat: Savings Application Preview screen#3053
feat: Savings Application Preview screen#3053itsPronay wants to merge 5 commits intoopenMF:developmentfrom
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
📝 WalkthroughWalkthroughThis PR introduces a new savings confirmation details screen integrated into the application flow, replacing the direct authentication navigation path. It restructures the savings application journey to include an explicit confirmation step between form submission and authentication. Additionally, field officer selection logic is removed from the apply screen. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant Apply as SavingsApply<br/>Screen
participant Fill as SavingsFill<br/>Application Screen
participant ApplyVM as SavingsApply<br/>ViewModel
participant FillVM as SavingsFill<br/>ViewModel
participant Confirm as SavingsConfirm<br/>Details Screen
participant ConfirmVM as SavingsConfirm<br/>ViewModel
participant Auth as Auth Flow
User->>Apply: Select Savings Product
Apply->>ApplyVM: FieldOfficerChange (removed)
ApplyVM-->>Apply: Update Field Officer State (removed)
User->>Apply: Click Continue
Apply->>ApplyVM: (removed: GetFieldOfficer)
ApplyVM->>Apply: navigateToFillDetailsScreen(productId, productName)
Note over Apply,Fill: New Flow: Direct to Fill Details
Apply->>Fill: Navigate with (productId, productName)
User->>Fill: Fill Application Form
Fill->>FillVM: Submit Form
FillVM-->>FillVM: Build NavigateToConfirmDetails<br/>Event with payload
FillVM->>Confirm: navigateToSavingsConfirmDetailsScreen(...)
Confirm->>ConfirmVM: Initialize with Route Data
ConfirmVM-->>Confirm: Render Confirmation Details
User->>Confirm: Review & Click Apply
Confirm->>ConfirmVM: SubmitSavingsAccount Action
ConfirmVM->>Auth: navigateToAuthenticateScreen()
rect rgb(200, 220, 255)
Note over FillVM,Auth: Removed: Direct Auth from<br/>Fill Application Screen
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.kt (2)
278-326: Dead code:handleFieldOfficersfunction is never called.The
handleFieldOfficersfunction remains in the ViewModel but is no longer invoked after the field officer logic was removed. This should be deleted to avoid confusion.🔎 Remove unused function
- /** - * Handles the result of the `fetchFieldOfficer` network call. - * On success, it maps the field officer options and updates the state. - * On failure, it shows an error dialog and navigates back. - * - * @param template The [DataState] containing the savings template data, - * including field officer options. - */ - private fun handleFieldOfficers(template: DataState<SavingsAccountTemplate?>) { - when (template) { - is DataState.Loading -> { - updateState { - it.copy( - showOverlay = true, - ) - } - } - is DataState.Success -> { - updateState { - it.copy( - showOverlay = false, - ) - } - val mappedSavingsFieldOfficer: Map<Long, String> = template.data?.fieldOfficerOptions - ?.mapNotNull { option -> - val id = option.id?.toLong() - val name = option.displayName - if (id != null && name != null) id to name else null - } - ?.takeIf { it.isNotEmpty() } - ?.toMap() ?: emptyMap() - - updateState { - it.copy( - savingsProductTemplate = template.data, - ) - } - } - - is DataState.Error -> { - updateState { - it.copy( - showOverlay = false, - ) - } - updateState { - it.copy( - uiState = if (template.exception.cause is IOException) { - ScreenUiState.Network - } else { - ScreenUiState.Error(Res.string.feature_apply_savings_error_server) - }, - ) - } - } - } - }
504-537: Remove debugprintlnstatement and unused state field.Line 534 contains
println(todayMillis)which is debug code that should be removed before merging. Additionally,selectedFieldOfficerId(line 511) appears unused after the field officer logic removal.🔎 Proposed cleanup
@OptIn(ExperimentalTime::class) val submittedOnDate: String get() { val todayMillis = Clock.System.now().toEpochMilliseconds() - println(todayMillis) return DateHelper.getDateMonthYearString(todayMillis) }Also consider removing
selectedFieldOfficerIdfrom the state if it's no longer used.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyScreen.kt (2)
64-66: Update documentation to reflect the removal of officer information.The KDoc at line 64-65 states the lambda passes "product and officer information," but the actual signature now only passes product ID and product name. Field officer selection has been removed from this flow.
🔎 Suggested documentation fix
* @param navigateBack A lambda function to handle back navigation events. * @param navigateToFillDetailsScreen A lambda to navigate to the detailed application form, -* passing product and officer information. +* passing the selected product ID and product name. * @param viewModel The ViewModel responsible for the screen's logic and state.
140-149: Update function documentation to remove reference to field officer dropdown.The KDoc mentions "dropdowns for selecting a savings product and a field officer," but the field officer dropdown has been removed from the UI in this PR.
🔎 Suggested documentation fix
/** * A stateless composable that renders the main UI for the "Apply for Savings" screen. * * It conditionally displays UI based on the [ScreenUiState] (e.g., loading, error, success). - * The success state includes dropdowns for selecting a savings product and a field officer. + * The success state includes a dropdown for selecting a savings product. * * @param state The current [SavingsApplicationState] to render. * @param onAction A callback to send user actions to the ViewModel. * @param modifier The [Modifier] to be applied to the layout. */
🧹 Nitpick comments (8)
config/detekt/detekt.yml (1)
803-803: Consider refactoring navigation code instead of relaxing the line length limit.Increasing the line length limit from 120 to 140 to accommodate long navigation parameter lists may mask an underlying design issue. If the new navigation code has parameter lists that exceed 120 characters, consider:
- Using parameter objects or data classes to group related parameters
- Breaking down complex navigation signatures into smaller, more focused functions
- Applying the builder pattern for complex navigation scenarios
Relaxing linting standards project-wide to fit new code can gradually erode code quality over time.
</review_comment_end>
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt (2)
116-123: Auth result collection lacks cancellation awareness.The
observeAuthResult()coroutine collects indefinitely without checking for cancellation. WhileviewModelScopehandles cancellation on ViewModel clear, consider usinglaunchInwithonEachfor cleaner flow handling, or ensure this pattern is intentional.
180-189: Redundant coroutine launch when sending action.In
handleSavingsApplyRequest, theviewModelScope.launchwrapper aroundsendActionis unnecessary ifsendActionis already a synchronous or non-blocking call. This adds overhead without benefit.🔎 Proposed simplification
private fun handleSavingsApplyRequest(isAuthenticated: Boolean) { if (isAuthenticated) { - viewModelScope.launch { - sendAction(SavingsConfirmDetailsAction.Internal.ApplySavings) - } + sendAction(SavingsConfirmDetailsAction.Internal.ApplySavings) } else { // Authentication failed or was cancelled, hide any loading state updateState { it.copy(showOverlay = false) } } }feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.kt (1)
79-79: Consider using a data class for navigation parameters.The
navigateToConfirmDetailsScreencallback with 10 positional parameters is error-prone and hard to maintain. A data class would provide type safety and named parameters.🔎 Suggested approach
Define a data class for navigation parameters:
data class ConfirmDetailsNavArgs( val savingsProductId: Long, val applicantName: String, val savingsProductName: String, val currency: String, val minOpeningBalance: String, val frequency: String, val frequencyTypeName: String, val frequencyTypeId: Long, val allowOverdraft: Boolean, val submittedOnDate: String, )Then update the callback signature:
- navigateToConfirmDetailsScreen: (Long, String, String, String, String, String, String, Long, Boolean, String) -> Unit, + navigateToConfirmDetailsScreen: (ConfirmDetailsNavArgs) -> Unit,feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.kt (1)
65-73: Same 10-parameter callback concern applies here.The
navigateToConfirmDetailsScreencallback signature insavingsFillApplicationDestinationhas the same maintainability concern noted earlier. Consider a data class approach for consistency across the navigation layer.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.kt (1)
196-196: Stray empty line - minor formatting.Line 196 appears to be an extra blank line that could be cleaned up for consistency.
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.kt (1)
35-35: Add KDoc for consistency with other ViewModel registrations.The
SavingsConfirmDetailsViewModelregistration lacks the descriptive comment that other ViewModels have (lines 25-28 and 30-33). Consider adding one for consistency.🔎 Suggested addition
viewModelOf(::SavingsFillApplicationViewModel) + /** + * Provides an instance of [SavingsConfirmDetailsViewModel]. + * This ViewModel handles the logic for the savings confirmation preview screen. + */ viewModelOf(::SavingsConfirmDetailsViewModel)feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.kt (1)
32-44: Consider reducing parameter count for improved maintainability.The route has 10 parameters, which makes the API somewhat brittle. Any future changes to the confirmation screen's data requirements will necessitate updates across multiple call sites.
Consider one of these alternatives:
- Pass only
savingsProductIdand load the full details in the ViewModel- Create a dedicated serializable wrapper class for the confirmation data
However, for a preview/confirmation screen where all data is already collected and needs to be displayed, the current approach is acceptable and avoids additional data loading overhead.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
config/detekt/detekt.ymlfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/navigation/SavingsApplicationNavGraph.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.kt
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{kt,kts}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{kt,kts}: Use Koin for dependency injection across all platforms
Implement ScreenState pattern for handling loading/success/error states in features
Files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/navigation/SavingsApplicationNavGraph.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt
**/*ViewModel.kt
📄 CodeRabbit inference engine (CLAUDE.md)
Use ViewModels with StateFlow/SharedFlow for state management
Files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.kt
🧠 Learnings (5)
📚 Learning: 2025-12-29T16:08:08.437Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T16:08:08.437Z
Learning: Applies to **/*ViewModel.kt : Use ViewModels with StateFlow/SharedFlow for state management
Applied to files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt
📚 Learning: 2025-12-29T16:08:08.437Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T16:08:08.437Z
Learning: Applies to **/*.{kt,kts} : Implement ScreenState<T> pattern for handling loading/success/error states in features
Applied to files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.ktfeature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt
📚 Learning: 2025-12-29T16:08:08.437Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T16:08:08.437Z
Learning: Define Koin modules in the di/ package of each module
Applied to files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.kt
📚 Learning: 2025-12-29T16:08:08.437Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T16:08:08.437Z
Learning: Applies to **/*.{kt,kts} : Use Koin for dependency injection across all platforms
Applied to files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.kt
📚 Learning: 2025-12-29T16:08:08.437Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T16:08:08.437Z
Learning: Use Jetbrains Compose Navigation with graph hierarchy: ROOT_GRAPH → AUTH_GRAPH → PASSCODE_GRAPH → MAIN_GRAPH
Applied to files:
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.kt
🧬 Code graph analysis (5)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt (2)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.kt (2)
updateState(173-175)dismissDialog(251-255)feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.kt (2)
updateState(158-160)dismissDialog(448-452)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.kt (1)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.kt (1)
SavingsFillApplicationScreen(76-119)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.kt (1)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt (1)
SavingsConfirmDetailsScreen(56-95)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/navigation/SavingsApplicationNavGraph.kt (1)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.kt (1)
savingsConfirmDetailsDestination(85-97)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt (5)
core/designsystem/src/commonMain/kotlin/org/mifos/mobile/core/designsystem/component/MifosScaffold.kt (1)
MifosElevatedScaffold(209-284)core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosPoweredCard.kt (1)
MifosPoweredCard(39-78)core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosErrorComponent.kt (1)
MifosErrorComponent(43-62)core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosProgressIndicator.kt (2)
MifosProgressIndicator(33-60)MifosProgressIndicatorOverlay(62-95)core/ui/src/commonMain/kotlin/org/mifos/mobile/core/ui/component/MifosDetailsCard.kt (1)
MifosDetailsCard(32-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PR Checks KMP / Static Analysis Check
🔇 Additional comments (20)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt (2)
273-289: State class follows ScreenState pattern correctly.The
SavingsConfirmDetailsStatedata class properly implements the UI state pattern withshowOverlay,dialogState, anduiState: ScreenUiState. This aligns with the coding guidelines for handling loading/success/error states.
295-316: Events and Actions follow the recommended sealed interface pattern.The separation of
SavingsConfirmDetailsEventfor one-time UI effects andSavingsConfirmDetailsActionfor user/internal actions is well-structured and follows the established architecture in this codebase.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationScreen.kt (2)
88-101: Event handling for navigation is correctly wired.The
NavigateToConfirmDetailsevent correctly maps all fields from the event to the navigation callback. The implementation aligns with the new confirm-details flow.
306-317: Button no longer validates form before enabling.The
MifosButtonfor "Next" is now always enabled (theenabled = state.isFormValidbinding was removed). This shifts validation to click-time. EnsureSavingsApplicationAction.NavigateToAuthenticationtriggers validation in the ViewModel before proceeding.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationRoute.kt (1)
29-33: Route data class correctly simplified.The
SavingsFillApplicationRoutenow carriessavingsProductNameinstead of field officer details, aligning with the removal of field officer logic from this flow. This is a clean simplification.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.kt (1)
128-151: Action handling is clean and follows established patterns.The
handleActionfunction properly dispatches actions to appropriate handlers. The switch toNavigateToConfirmDetailsaction aligns with the new flow.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/fillApplication/FillApplicationViewModel.kt (4)
299-321: Validation logic correctly handles optional fields.The validation now properly allows empty values for the minimum opening balance field by checking for the
validation_amount_emptyerror resource and returningValidationResult.Successin that case. This aligns with the PR objective of making non-required fields optional.
356-371: Frequency validation correctly treats empty as valid.The frequency field validation now explicitly returns
ValidationResult.Successfor empty/trimmed-empty input, making it optional as intended.
481-494: Verify theallowOverdraftconditional logic.Line 491 uses
if (state.isOverDraftAllowed) state.checked else state.isOverDraftAllowed. This means if overdraft is not allowed by the product, it will passfalse. If allowed, it passes the user's checkbox state. Confirm this is the intended behavior.
673-684: Event payload is comprehensive and well-structured.The
NavigateToConfirmDetailsevent carries all necessary data for the confirmation screen, replacing the previous authentication-first approach. This supports the new preview-before-submit flow.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/di/SavingsApplicationModule.kt (1)
24-35: DI module correctly registers all required ViewModels.The Koin module now properly provides all three ViewModels for the savings application flow:
SavingsApplyViewModel,SavingsFillApplicationViewModel, and the newSavingsConfirmDetailsViewModel. This follows the project's DI conventions.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt (3)
56-95: Screen composable correctly wires ViewModel and navigation.The
SavingsConfirmDetailsScreenproperly:
- Collects state with lifecycle awareness
- Handles all navigation events (back, status, authenticate)
- Delegates UI to content and dialog composables
- Uses
rememberfor action callbacks to prevent unnecessary recompositionThis follows the established patterns in the codebase.
149-195: UI state handling covers all cases appropriately.The
whenblock handlesError,Loading,Network, andSuccessstates correctly. The success state properly displays the details card and action button with overlay support.
103-103: Remove the unnecessary@OptIn(ExperimentalMaterial3Api::class)annotation.
SavingsConfirmDetailsDialogdoesn't directly use any experimental Material3 APIs. It only callsMifosBasicDialog, which uses stable APIs likeAlertDialogand doesn't require the opt-in annotation. Removing it reduces unnecessary annotation clutter.feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyScreen.kt (1)
234-246: LGTM!The button enablement logic correctly checks for a selected savings product. This aligns with the removal of field officer selection from this screen.
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyRoute.kt (1)
46-59: LGTM!The navigation parameter type update is consistent with the removal of field officer selection. The documentation accurately describes the new parameters (product ID and product name).
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/navigation/SavingsApplicationNavGraph.kt (2)
17-18: LGTM!The new imports for the confirmation details screen are correctly added and used in the navigation graph.
69-78: LGTM!The navigation wiring correctly integrates the new confirmation details screen into the savings application flow. The callbacks are properly wired to their respective destinations:
- Fill Application → Confirm Details
- Confirm Details → Authentication or Status
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsRoute.kt (2)
46-76: LGTM!The navigation function correctly constructs the route with all required parameters and follows the standard pattern for navigation extensions in this codebase.
78-97: LGTM!The destination registration correctly wires the confirmation screen into the navigation graph with appropriate callbacks for authentication, status display, and back navigation.
| MifosElevatedScaffold( | ||
| onNavigateBack = { onAction(SavingsConfirmDetailsAction.OnNavigateBack) }, | ||
| topBarTitle = "Savings Confirmation", | ||
| bottomBar = { | ||
| Surface { | ||
| MifosPoweredCard( | ||
| modifier = modifier | ||
| .fillMaxWidth() | ||
| .navigationBarsPadding(), | ||
| ) | ||
| } | ||
| }, |
There was a problem hiding this comment.
Use string resource instead of hardcoded text.
Line 138 uses the hardcoded string "Savings Confirmation" for the top bar title. This should use a string resource for internationalization support.
🔎 Suggested fix
Add a string resource and use it:
MifosElevatedScaffold(
onNavigateBack = { onAction(SavingsConfirmDetailsAction.OnNavigateBack) },
- topBarTitle = "Savings Confirmation",
+ topBarTitle = stringResource(Res.string.feature_apply_savings_confirmation_title),Ensure the string resource is defined in the appropriate resources file.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsScreen.kt
around lines 136-147, the topBarTitle currently uses the hardcoded string
"Savings Confirmation"; replace this with a string resource reference (e.g. use
stringResource(R.string.savings_confirmation) or the appropriate multiplatform
resources accessor) and add the key savings_confirmation to the app's string
resources (strings.xml or the shared resources file) with the same text to
enable i18n; ensure imports are added for stringResource (or the
platform-specific resource helper) and rebuild to verify no unresolved
references.
| initialState = run { | ||
| val route = savedStateHandle.toRoute<SavingsConfirmDetailsRoute>() | ||
| // Build details map, filtering out empty optional fields | ||
| val detailMap = buildMap { | ||
| // Always show required fields | ||
| put(Res.string.feature_apply_savings_label_applicant_name, route.applicantName) | ||
| put(Res.string.feature_apply_savings_label_savings_product, route.savingsProductName) | ||
| put(Res.string.feature_apply_savings_label_submission_date, route.submittedOnDate) | ||
| put(Res.string.feature_apply_savings_label_currency, route.currency) | ||
|
|
||
| // Only show optional fields if they have values | ||
| if (route.minOpeningBalance.trim().isNotEmpty()) { | ||
| put(Res.string.feature_apply_savings_label_minimum_opening_balance, route.minOpeningBalance) | ||
| } | ||
| if (route.frequency.trim().isNotEmpty()) { | ||
| put(Res.string.feature_apply_savings_label_frequency, route.frequency) | ||
| } | ||
| if (route.frequencyTypeName.trim().isNotEmpty()) { | ||
| put(Res.string.feature_apply_savings_label_frequency_type, route.frequencyTypeName) | ||
| } | ||
| if (route.allowOverdraft) { | ||
| put(Res.string.feature_apply_savings_label_overdraft, "Yes") | ||
| } | ||
| } | ||
|
|
||
| SavingsConfirmDetailsState( | ||
| clientId = requireNotNull(userPreferencesRepositoryImpl.clientId.value), | ||
| details = detailMap, | ||
| savingsProductId = route.savingsProductId, | ||
| savingsProductName = route.savingsProductName, | ||
| currency = route.currency, | ||
| minOpeningBalance = route.minOpeningBalance, | ||
| frequency = route.frequency, | ||
| frequencyTypeName = route.frequencyTypeName, | ||
| frequencyTypeId = route.frequencyTypeId, | ||
| allowOverdraft = route.allowOverdraft, | ||
| submittedOnDate = route.submittedOnDate, | ||
| ) | ||
| }, |
There was a problem hiding this comment.
Potential crash if clientId is null during initialization.
Using requireNotNull on userPreferencesRepositoryImpl.clientId.value within the initialState run block will throw an IllegalArgumentException if the client ID is unexpectedly null. This could crash the app before the ViewModel is fully initialized.
Consider defensive handling or ensuring the navigation to this screen is gated by a valid client session.
🔎 Suggested safer approach
- clientId = requireNotNull(userPreferencesRepositoryImpl.clientId.value),
+ clientId = userPreferencesRepositoryImpl.clientId.value ?: run {
+ // Handle the missing client ID gracefully, e.g., navigate back or show error
+ 0L // Placeholder - should trigger appropriate error handling
+ },Alternatively, validate the client session before navigating to this screen.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt
around lines 67–105, the use of
requireNotNull(userPreferencesRepositoryImpl.clientId.value) during initialState
construction can throw and crash the app if clientId is null; replace this with
defensive handling: obtain clientId via a safe nullable read (e.g., val clientId
= userPreferencesRepositoryImpl.clientId.value), and if null either provide a
sensible fallback or change the state construction to handle a null clientId
(e.g., set clientId in SavingsConfirmDetailsState to null and add an
error/unauthenticated flag), or short-circuit initialization by emitting a
navigation/error event so the screen is not shown; ensure no requireNotNull is
used in initialState and that downstream code handles a nullable clientId or the
VM redirects when clientId is missing.
| private fun getSavingsPayload() = SavingsAccountApplicationPayload( | ||
| clientId = state.clientId.toInt(), | ||
| productId = state.savingsProductId.toInt(), | ||
| submittedOnDate = state.submittedOnDate, | ||
| minRequiredOpeningBalance = state.minOpeningBalance.trim().toDoubleOrNull(), | ||
| lockinPeriodFrequency = state.frequency.trim().toIntOrNull(), | ||
| lockinPeriodFrequencyType = if (state.frequencyTypeId > 0) state.frequencyTypeId.toInt() else null, | ||
| allowOverdraft = state.allowOverdraft, | ||
| locale = "en", | ||
| dateFormat = "dd MMMM yyyy", | ||
| monthDayFormat = "dd MMM", | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
ast-grep --pattern 'data class SavingsAccountApplicationPayload($$$)'Repository: openMF/mifos-mobile
Length of output: 45
🏁 Script executed:
rg "SavingsAccountApplicationPayload" --type kt -B 2 -A 10Repository: openMF/mifos-mobile
Length of output: 89
🏁 Script executed:
rg "SavingsAccountApplicationPayload" -B 2 -A 10Repository: openMF/mifos-mobile
Length of output: 27912
🏁 Script executed:
cat feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt | head -150Repository: openMF/mifos-mobile
Length of output: 7738
🏁 Script executed:
rg "data class SavingsConfirmDetailsState" -A 20Repository: openMF/mifos-mobile
Length of output: 3639
Verify type compatibility for ID conversions to prevent silent truncation.
state.clientId.toInt() and state.savingsProductId.toInt() convert Long values to Int. Since SavingsAccountApplicationPayload requires Int fields for clientId and productId, ensure client and product IDs never exceed Int.MAX_VALUE (2,147,483,647). If they can, this will cause silent data truncation and corruption. Consider either:
- Validating IDs at the point of conversion to ensure they fit within Int bounds, or
- Updating the payload class to accept
Longif the backend API supports it.
🤖 Prompt for AI Agents
In
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/confirmDetails/ConfirmDetailsViewModel.kt
around lines 256 to 267, the code unconditionally converts state.clientId and
state.savingsProductId from Long to Int which can silently truncate values >
Int.MAX_VALUE; either validate that each ID fits within Int range before calling
toInt() (throw or surface an error if out of range) or change
SavingsAccountApplicationPayload to accept Long for clientId/productId and
propagate that type through the call chain (and update any serialization/backend
contracts accordingly) so no unsafe narrowing occurs.
Fixes - Jira-#Issue_Number
Didn't create a Jira ticket, click here to create new.
Please Add Screenshots If there are any UI changes.
Please make sure these boxes are checked before submitting your pull request - thanks!
Run the static analysis check
./gradlew checkorci-prepush.shto make sure you didn't break anythingIf you have multiple commits please combine them into one commit by squashing them.
Summary by CodeRabbit
Release Notes
New Features
Refactoring
Style
✏️ Tip: You can customize this high-level summary in your review settings.