Skip to content

feat(kyc): implement KYC Level 3 review and submit screen#2017

Open
IITI-tushar wants to merge 1 commit into
openMF:developmentfrom
IITI-tushar:feat/kyc-implement-kyc-level-3-review-and-submit-screen
Open

feat(kyc): implement KYC Level 3 review and submit screen#2017
IITI-tushar wants to merge 1 commit into
openMF:developmentfrom
IITI-tushar:feat/kyc-implement-kyc-level-3-review-and-submit-screen

Conversation

@IITI-tushar
Copy link
Copy Markdown

@IITI-tushar IITI-tushar commented May 9, 2026

Issue Fix

Fixes #2016
Jira Task: Task_Number

Description

This pull request implements the complete KYC Level 3 screen and its supporting view model, enabling users to review their details and submit for KYC Level 3 verification. The changes introduce state management, UI composition, event handling, and dialog support for loading and error states.

KYC Level 3 Screen Implementation and State Management:

UI and User Flow:

  • Introduced a fully functional KYCLevel3Screen that displays user KYC details in a review format, allows confirmation and submission, and handles navigation and completion events.
  • Added composables for displaying section headers, review rows, and dialogs for loading and error states.

ViewModel and State Logic:

  • Implemented KYCLevel3ViewModel to manage screen state, fetch KYC details, handle user actions (confirm/submit, navigate back), and update the UI based on repository responses.
  • Defined KycLevel3State, KycLevel3Event, and KycLevel3Action to structure state, events, and actions for the screen.

Repository Integration:

  • Integrated with KycLevelRepository and UserPreferencesRepository to fetch and update KYC details, marking the KYC process as complete on successful submission.

UI Feedback:

  • Added loading and error dialog support to provide feedback during network operations and error conditions.

  • Apply the AndroidStyle.xml style template to your code in Android Studio.

  • Run the unit tests with ./gradlew check 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
    • KYC Level 3 review screen is now fully functional and interactive, displaying personal details and address information organized into clear sections.
    • "Confirm & Submit" action button enables users to finalize and complete the KYC Level 3 verification process.
    • Improved user experience with loading indicators and detailed error dialogs that provide clear feedback during submission.

Review Change Stack

Signed-off-by: Tushar Saxena <019saxenatushar@gmail.com>
@IITI-tushar IITI-tushar requested a review from a team May 9, 2026 10:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

This PR implements the complete KYC Level 3 "Review & Submit" screen by replacing placeholder code with a fully functional Compose UI and business logic ViewModel. The ViewModel fetches KYC Level 1 details on init, manages loading/error dialog states, and persists KYC Level 3 completion to storage on user confirmation. The screen displays fetched details in grouped review rows and provides a bottom submit button with dialog feedback.

Changes

KYC Level 3 Feature Implementation

Layer / File(s) Summary
State & Event Contracts
feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt
KycLevel3State holds clientId, nullable kycDetails, loading flag, and nullable DialogState (Loading/Error). KycLevel3Event emits OnNavigateBack and OnKycComplete. KycLevel3Action routes NavigateBack, ConfirmAndSubmit, and internal HandleLevel1Result.
ViewModel Implementation
feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt
Constructor accepts KycLevelRepository and UserPreferencesRepository. Fetches Level 1 details on init and updates isLoading/kycDetails state. Handles ConfirmAndSubmit by updating persisted KYC level to KYC_LEVEL_3 and emitting completion or error dialog.
Screen Composition
feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt
Collects state from ViewModel, shows loading indicator or LazyColumn of review rows grouped by "Personal Details" and "Address" headers. Adds ReviewRow and SectionHeader composables. Bottom button dispatches ConfirmAndSubmit. KycLevel3Dialogs renders loading or error dialogs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops with delight at KYC complete,
Three levels merged, the flow is neat,
From stubs to state, the UI springs,
Review, confirm—a rabbit sings! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ 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(kyc): implement KYC Level 3 review and submit screen' accurately and concisely describes the main change—implementing a functional KYC Level 3 screen with review and submission functionality.
Linked Issues check ✅ Passed The PR fulfills all coding requirements from issue #2016: replaces the stubbed KYCLevel3Screen with functional UI, implements KYCLevel3ViewModel with data/action handling, enables users to review/confirm/submit KYC details, and integrates repositories for data fetching and state persistence.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing KYC Level 3 screen and ViewModel as specified in issue #2016; no unrelated changes detected.

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

Copy link
Copy Markdown
Contributor

@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: 4

🤖 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
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt`:
- Around line 40-43: The current onDismiss for KycLevel3Dialogs calls
viewModel.trySendAction(KycLevel3Action.NavigateBack) which navigates away on
any dialog dismissal; add a new action (e.g., KycLevel3Action.DismissDialog or
ClearDialog) to the KycLevel3Action sealed class, handle it in the ViewModel to
clear state.dialogState, and change the KycLevel3Dialogs call to use onDismiss =
{ viewModel.trySendAction(KycLevel3Action.DismissDialog) } so dismissing the
error simply clears the dialog without navigating away.

In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`:
- Around line 44-45: In KYCLevel3ViewModel, the early return when
state.kycDetails is null (the line referencing state.kycDetails?.copy(...))
leaves dialogState stuck in loading; update the null branch to first set
dialogState to a non-loading state (e.g., dismiss the dialog or set an error
state/message) before returning so the UI stops showing the spinner—locate the
submit/launch block that references state.kycDetails and ensure dialogState is
updated (cleared or set to an error) whenever state.kycDetails is null instead
of simply returning.
- Around line 22-24: The ViewModel currently force-unwraps clientId in
KYCLevel3ViewModel initialization (initialState = KycLevel3State(clientId =
requireNotNull(userPreferencesRepository.clientId.value))), which can crash if
clientId is not set; change this to a safe initialization by not using
requireNotNull — set KycLevel3State.clientId to a nullable or default value
(e.g., null or empty string) and defer real clientId usage to when it becomes
available (subscribe/observe userPreferencesRepository.clientId and update the
state), and handle null/default in any methods that use clientId (refer to
KYCLevel3ViewModel, KycLevel3State, and userPreferencesRepository.clientId).
- Line 43: Add the missing imports required for using viewModelScope.launch and
the KycLevel enum: import the coroutine extension (kotlinx.coroutines.launch) so
viewModelScope.launch resolves, and import the KycLevel enum/type that defines
KYC_LEVEL_3 (the package where KycLevel is declared in this module) so
KycLevel.KYC_LEVEL_3 compiles; update the KYCLevel3ViewModel imports to include
these symbols.
🪄 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: 47f28755-b8e0-4d0e-b166-9ace191a3ddf

📥 Commits

Reviewing files that changed from the base of the PR and between d399e0a and 543e505.

📒 Files selected for processing (2)
  • feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt
  • feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt

Comment on lines +40 to 43
KycLevel3Dialogs(
dialogState = state.dialogState,
onDismiss = { viewModel.trySendAction(KycLevel3Action.NavigateBack) },
)
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 | 🟠 Major | ⚡ Quick win

UX concern: Error dialog dismissal navigates away from screen.

When the user dismisses an error dialog, they're navigated back to the previous screen. This prevents retry attempts and may frustrate users who want to fix the issue and resubmit. Typically, dismissing an error dialog should just close the dialog while keeping the user on the current screen.

💡 Suggested UX improvement

Add a dedicated dismiss action to clear the dialog state:

+// In KycLevel3Action
+sealed interface KycLevel3Action {
+    data object NavigateBack : KycLevel3Action
+    data object ConfirmAndSubmit : KycLevel3Action
+    data object DismissDialog : KycLevel3Action
+    sealed interface Internal : KycLevel3Action {
+        data class HandleLevel1Result(val result: DataState<KYCLevel1Details?>) : Internal
+    }
+}
+
+// In KYCLevel3ViewModel.handleAction
+when (action) {
+    KycLevel3Action.NavigateBack -> sendEvent(KycLevel3Event.OnNavigateBack)
+    KycLevel3Action.ConfirmAndSubmit -> handleConfirmSubmit()
+    KycLevel3Action.DismissDialog -> mutableStateFlow.update { it.copy(dialogState = null) }
+    is HandleLevel1Result -> handleLevel1Result(action)
+}

Then in the screen:

     KycLevel3Dialogs(
         dialogState = state.dialogState,
-        onDismiss = { viewModel.trySendAction(KycLevel3Action.NavigateBack) },
+        onDismiss = { viewModel.trySendAction(KycLevel3Action.DismissDialog) },
     )
🤖 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/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt`
around lines 40 - 43, The current onDismiss for KycLevel3Dialogs calls
viewModel.trySendAction(KycLevel3Action.NavigateBack) which navigates away on
any dialog dismissal; add a new action (e.g., KycLevel3Action.DismissDialog or
ClearDialog) to the KycLevel3Action sealed class, handle it in the ViewModel to
clear state.dialogState, and change the KycLevel3Dialogs call to use onDismiss =
{ viewModel.trySendAction(KycLevel3Action.DismissDialog) } so dismissing the
error simply clears the dialog without navigating away.

Comment on lines +22 to +24
initialState = KycLevel3State(
clientId = requireNotNull(userPreferencesRepository.clientId.value),
),
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 | ⚡ Quick win

Critical: Potential crash if clientId is null at ViewModel initialization.

Accessing userPreferencesRepository.clientId.value with requireNotNull during ViewModel construction will crash the app if the client ID hasn't been set yet. This creates a fragile initialization dependency.

Consider deferring clientId access or handling the null case gracefully.

🛡️ Proposed safer initialization
-) : BaseViewModel<KycLevel3State, KycLevel3Event, KycLevel3Action>(
-    initialState = KycLevel3State(
-        clientId = requireNotNull(userPreferencesRepository.clientId.value),
-    ),
-) {
+) : BaseViewModel<KycLevel3State, KycLevel3Event, KycLevel3Action>(
+    initialState = KycLevel3State(
+        clientId = userPreferencesRepository.clientId.value ?: 0L,
+    ),
+) {
     init {
+        if (state.clientId == 0L) {
+            mutableStateFlow.update { 
+                it.copy(
+                    isLoading = false,
+                    dialogState = KycLevel3State.DialogState.Error("Client ID not found")
+                )
+            }
+            return
+        }
         kycLevelRepository.fetchKYCLevel1Details(state.clientId)
🤖 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/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
around lines 22 - 24, The ViewModel currently force-unwraps clientId in
KYCLevel3ViewModel initialization (initialState = KycLevel3State(clientId =
requireNotNull(userPreferencesRepository.clientId.value))), which can crash if
clientId is not set; change this to a safe initialization by not using
requireNotNull — set KycLevel3State.clientId to a nullable or default value
(e.g., null or empty string) and defer real clientId usage to when it becomes
available (subscribe/observe userPreferencesRepository.clientId and update the
state), and handle null/default in any methods that use clientId (refer to
KYCLevel3ViewModel, KycLevel3State, and userPreferencesRepository.clientId).

private fun handleConfirmSubmit() {
mutableStateFlow.update { it.copy(dialogState = KycLevel3State.DialogState.Loading) }
// KYC Level 3 marks the current level as complete — update level field
viewModelScope.launch {
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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the missing import and check if launch is used elsewhere in the codebase

# Check current imports in the file
echo "=== Current imports in KYCLevel3ViewModel.kt ==="
rg -n "^import" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt

# Search for correct import pattern in other ViewModels
echo -e "\n=== Examples of launch usage in other ViewModels ==="
rg -n "import kotlinx.coroutines.launch" --type=kt -g '*ViewModel*' -C 1

Repository: openMF/mifos-pay

Length of output: 731


🏁 Script executed:

cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt | sed -n '40,50p'

Repository: openMF/mifos-pay

Length of output: 805


🏁 Script executed:

cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt | sed -n '1,30p'

Repository: openMF/mifos-pay

Length of output: 1389


🏁 Script executed:

rg "KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt -n | head -20

Repository: openMF/mifos-pay

Length of output: 1291


🏁 Script executed:

rg "^import.*KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt

Repository: openMF/mifos-pay

Length of output: 196


🏁 Script executed:

fd "KycLevel" --type f -e kt | head -20

Repository: openMF/mifos-pay

Length of output: 244


🏁 Script executed:

rg "enum class KycLevel|object KycLevel" --type-list | head -20

Repository: openMF/mifos-pay

Length of output: 450


🏁 Script executed:

rg "enum class KycLevel|object KycLevel" -A 2

Repository: openMF/mifos-pay

Length of output: 382


🏁 Script executed:

rg "import.*KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCDescriptionViewModel.kt

Repository: openMF/mifos-pay

Length of output: 120


🏁 Script executed:

cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCDescriptionViewModel.kt | grep -A 5 "enum class KycLevel"

Repository: openMF/mifos-pay

Length of output: 276


🏁 Script executed:

rg "KycLevel\." -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/ | grep -v "KycLevel3\|KycLevelRepository"

Repository: openMF/mifos-pay

Length of output: 660


Multiple missing imports cause compilation errors.

The code uses viewModelScope.launch (line 43) and KycLevel.KYC_LEVEL_3 (line 44) but neither launch nor KycLevel are imported. Both are required for compilation.

🔧 Add missing imports
 import androidx.lifecycle.viewModelScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
 import org.mifospay.core.common.DataState
 import org.mifospay.core.data.repository.KycLevelRepository
 import org.mifospay.core.datastore.UserPreferencesRepository
 import org.mifospay.core.model.kyc.KYCLevel1Details
 import org.mifospay.core.ui.utils.BaseViewModel
+import org.mifospay.feature.kyc.KycLevel
 import org.mifospay.feature.kyc.KycLevel3Action.Internal.HandleLevel1Result
🤖 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/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
at line 43, Add the missing imports required for using viewModelScope.launch and
the KycLevel enum: import the coroutine extension (kotlinx.coroutines.launch) so
viewModelScope.launch resolves, and import the KycLevel enum/type that defines
KYC_LEVEL_3 (the package where KycLevel is declared in this module) so
KycLevel.KYC_LEVEL_3 compiles; update the KYCLevel3ViewModel imports to include
these symbols.

Comment on lines +44 to +45
val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name)
?: return@launch
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 | 🟠 Major | ⚡ Quick win

Major: Dialog stuck in loading state if kycDetails is null.

If kycDetails is null when the user clicks submit, the function returns early without updating dialogState, leaving the loading dialog visible indefinitely. This creates a poor user experience where the UI appears frozen.

🔧 Handle the null case properly
     private fun handleConfirmSubmit() {
         mutableStateFlow.update { it.copy(dialogState = KycLevel3State.DialogState.Loading) }
         // KYC Level 3 marks the current level as complete — update level field
         viewModelScope.launch {
             val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name)
-                ?: return@launch
+                ?: run {
+                    mutableStateFlow.update {
+                        it.copy(dialogState = KycLevel3State.DialogState.Error(
+                            "KYC details not available. Please try again."
+                        ))
+                    }
+                    return@launch
+                }
             val result = kycLevelRepository.updateKYCLevel1Details(state.clientId, details)
📝 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
val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name)
?: return@launch
val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name)
?: run {
mutableStateFlow.update {
it.copy(dialogState = KycLevel3State.DialogState.Error(
"KYC details not available. Please try again."
))
}
return@launch
}
🤖 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/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
around lines 44 - 45, In KYCLevel3ViewModel, the early return when
state.kycDetails is null (the line referencing state.kycDetails?.copy(...))
leaves dialogState stuck in loading; update the null branch to first set
dialogState to a non-loading state (e.g., dismiss the dialog or set an error
state/message) before returning so the UI stops showing the spinner—locate the
submit/launch block that references state.kycDetails and ensure dialogState is
updated (cleared or set to an error) whenever state.kycDetails is null instead
of simply returning.

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.

Implement KYC Level 3 "Review & Submit" Screen

1 participant