From 543e50509e0568c7161ba0b4d8da10c337f0dc9a Mon Sep 17 00:00:00 2001 From: Tushar Saxena <019saxenatushar@gmail.com> Date: Sat, 9 May 2026 15:55:32 +0530 Subject: [PATCH] feat/kyc implement kyc level 3 Review and Submit Signed-off-by: Tushar Saxena <019saxenatushar@gmail.com> --- .../mifospay/feature/kyc/KYCLevel3Screen.kt | 106 +++++++++++++----- .../feature/kyc/KYCLevel3ViewModel.kt | 101 +++++++++++++++-- 2 files changed, 173 insertions(+), 34 deletions(-) diff --git a/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt b/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt index f1295a6d9..588798381 100644 --- a/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt +++ b/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt @@ -9,45 +9,101 @@ */ package org.mifospay.feature.kyc -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.koin.compose.viewmodel.koinViewModel -import org.mifospay.core.designsystem.component.MifosScaffold +import org.mifospay.core.designsystem.component.* +import org.mifospay.core.ui.MifosProgressIndicator +import org.mifospay.core.ui.utils.EventsEffect +import template.core.base.designsystem.theme.KptTheme -// TODO:: Implement KYC Level 3 screen @Composable internal fun KYCLevel3Screen( navigateBack: () -> Unit, + onKycComplete: () -> Unit, modifier: Modifier = Modifier, viewModel: KYCLevel3ViewModel = koinViewModel(), ) { - KYCLevel3ScreenContent( - modifier = modifier, - navigateBack = navigateBack, + val state by viewModel.stateFlow.collectAsStateWithLifecycle() + + EventsEffect(viewModel) { event -> + when (event) { + KycLevel3Event.OnNavigateBack -> navigateBack() + KycLevel3Event.OnKycComplete -> onKycComplete() + } + } + + KycLevel3Dialogs( + dialogState = state.dialogState, + onDismiss = { viewModel.trySendAction(KycLevel3Action.NavigateBack) }, ) -} -@Composable -fun KYCLevel3ScreenContent( - modifier: Modifier = Modifier, - navigateBack: () -> Unit, -) { MifosScaffold( topBarTitle = "Review & Submit", - backPress = navigateBack, + backPress = { viewModel.trySendAction(KycLevel3Action.NavigateBack) }, modifier = modifier, - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - Text( - text = "KYC Level 3", - ) + ) { padding -> + if (state.isLoading) { + MifosProgressIndicator(modifier = Modifier.fillMaxSize()) + } else { + LazyColumn( + modifier = Modifier.fillMaxSize().padding(padding), + contentPadding = PaddingValues(KptTheme.spacing.md), + verticalArrangement = Arrangement.spacedBy(KptTheme.spacing.md), + ) { + state.kycDetails?.let { details -> + item { SectionHeader("Personal Details") } + item { ReviewRow("First Name", details.firstName) } + item { ReviewRow("Last Name", details.lastName) } + item { ReviewRow("Mobile Number", details.mobileNo) } + item { ReviewRow("Date of Birth", details.dob) } + item { SectionHeader("Address") } + item { ReviewRow("Address Line 1", details.addressLine1) } + item { ReviewRow("Address Line 2", details.addressLine2) } + } + item { + MifosButton( + onClick = { viewModel.trySendAction(KycLevel3Action.ConfirmAndSubmit) }, + modifier = Modifier.fillMaxWidth(), + ) { Text("Confirm & Submit") } + } + } } } } + +@Composable +private fun ReviewRow(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(text = label, style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant) + Text(text = value, style = MaterialTheme.typography.bodyMedium) + } + HorizontalDivider(modifier = Modifier.padding(top = KptTheme.spacing.sm)) +} + +@Composable +private fun SectionHeader(title: String) { + Text(text = title, style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(bottom = KptTheme.spacing.xs)) +} + +@Composable +private fun KycLevel3Dialogs(dialogState: KycLevel3State.DialogState?, onDismiss: () -> Unit) { + when (dialogState) { + is KycLevel3State.DialogState.Loading -> MifosLoadingDialog(LoadingDialogState.Shown) + is KycLevel3State.DialogState.Error -> MifosBasicDialog( + visibilityState = BasicDialogState.Shown(message = dialogState.message), + onDismissRequest = onDismiss, + ) + null -> Unit + } +} diff --git a/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt b/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt index d94877386..65aa0b8cb 100644 --- a/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt +++ b/feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt @@ -1,15 +1,98 @@ /* * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md + * ...license header... */ package org.mifospay.feature.kyc -import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +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.KycLevel3Action.Internal.HandleLevel1Result -// TODO: Implement KYC Level3 View Model -class KYCLevel3ViewModel : ViewModel() +class KYCLevel3ViewModel( + private val kycLevelRepository: KycLevelRepository, + private val userPreferencesRepository: UserPreferencesRepository, +) : BaseViewModel( + initialState = KycLevel3State( + clientId = requireNotNull(userPreferencesRepository.clientId.value), + ), +) { + init { + kycLevelRepository.fetchKYCLevel1Details(state.clientId) + .onEach { sendAction(HandleLevel1Result(it)) } + .launchIn(viewModelScope) + } + + override fun handleAction(action: KycLevel3Action) { + when (action) { + KycLevel3Action.NavigateBack -> sendEvent(KycLevel3Event.OnNavigateBack) + KycLevel3Action.ConfirmAndSubmit -> handleConfirmSubmit() + is HandleLevel1Result -> handleLevel1Result(action) + } + } + + 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 + val result = kycLevelRepository.updateKYCLevel1Details(state.clientId, details) + when (result) { + is DataState.Success -> { + mutableStateFlow.update { it.copy(dialogState = null) } + sendEvent(KycLevel3Event.OnKycComplete) + } + is DataState.Error -> { + mutableStateFlow.update { + it.copy(dialogState = KycLevel3State.DialogState.Error( + result.exception.message ?: "Submission failed" + )) + } + } + else -> Unit + } + } + } + + private fun handleLevel1Result(action: HandleLevel1Result) { + when (action.result) { + is DataState.Success -> mutableStateFlow.update { + it.copy(kycDetails = action.result.data, isLoading = false) + } + is DataState.Loading -> mutableStateFlow.update { it.copy(isLoading = true) } + else -> mutableStateFlow.update { it.copy(isLoading = false) } + } + } +} + +data class KycLevel3State( + val clientId: Long, + val kycDetails: KYCLevel1Details? = null, + val isLoading: Boolean = true, + val dialogState: DialogState? = null, +) { + sealed interface DialogState { + data object Loading : DialogState + data class Error(val message: String) : DialogState + } +} + +sealed interface KycLevel3Event { + data object OnNavigateBack : KycLevel3Event + data object OnKycComplete : KycLevel3Event +} + +sealed interface KycLevel3Action { + data object NavigateBack : KycLevel3Action + data object ConfirmAndSubmit : KycLevel3Action + sealed interface Internal : KycLevel3Action { + data class HandleLevel1Result(val result: DataState) : Internal + } +}