diff --git a/cmp-navigation/build.gradle.kts b/cmp-navigation/build.gradle.kts index b005d0dc5c..ec1eadeff1 100644 --- a/cmp-navigation/build.gradle.kts +++ b/cmp-navigation/build.gradle.kts @@ -46,6 +46,7 @@ kotlin { implementation(projects.feature.pathTracking) implementation(projects.feature.report) implementation(projects.feature.savings) + implementation(projects.feature.recurringDeposit) implementation(projects.feature.settings) implementation(projects.feature.search) diff --git a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt index 08425ac605..4d14674e00 100644 --- a/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt +++ b/cmp-navigation/src/commonMain/kotlin/cmp/navigation/di/KoinModules.kt @@ -31,6 +31,7 @@ import com.mifos.feature.loan.di.LoanModule import com.mifos.feature.note.di.NoteModule import com.mifos.feature.offline.di.OfflineModule import com.mifos.feature.path.tracking.di.PathTrackingModule +import com.mifos.feature.recurringDeposit.di.RecurringDepositModule import com.mifos.feature.report.di.ReportModule import com.mifos.feature.savings.di.SavingsModule import com.mifos.feature.search.di.SearchModule @@ -83,6 +84,7 @@ object KoinModules { OfflineModule, PathTrackingModule, ReportModule, + RecurringDepositModule, SavingsModule, SearchModule, SettingsModule, diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt index b6da3df34a..6744704f68 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repository/RecurringAccountRepository.kt @@ -17,9 +17,9 @@ import kotlinx.coroutines.flow.Flow interface RecurringAccountRepository { - fun getRecuttingAccountRepository(): Flow> + fun getRecurringAccountTemplate(): Flow> - fun getRecuttingAccountRepositoryBtProduct( + fun getRecurringAccountTemplateByProduct( clientId: Int, productId: Int, ): Flow> diff --git a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/RecurringAccountRepositoryImp.kt b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/RecurringAccountRepositoryImp.kt index 72ffcc43ca..11410bc7e6 100644 --- a/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/RecurringAccountRepositoryImp.kt +++ b/core/data/src/commonMain/kotlin/com/mifos/core/data/repositoryImp/RecurringAccountRepositoryImp.kt @@ -21,12 +21,12 @@ import kotlinx.coroutines.flow.Flow class RecurringAccountRepositoryImp( val dataManagerRecurringAccount: DataManagerRecurringAccount, ) : RecurringAccountRepository { - override fun getRecuttingAccountRepository(): Flow> { + override fun getRecurringAccountTemplate(): Flow> { return dataManagerRecurringAccount.getRecurringDepositAccountTemplate .asDataStateFlow() } - override fun getRecuttingAccountRepositoryBtProduct( + override fun getRecurringAccountTemplateByProduct( clientId: Int, productId: Int, ): Flow> { diff --git a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/payloads/RecurringDepositAccountPayload.kt b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/payloads/RecurringDepositAccountPayload.kt index fd3c1caa91..92201a86aa 100644 --- a/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/payloads/RecurringDepositAccountPayload.kt +++ b/core/model/src/commonMain/kotlin/com/mifos/core/model/objects/payloads/RecurringDepositAccountPayload.kt @@ -13,9 +13,9 @@ import kotlinx.serialization.Serializable @Serializable data class RecurringDepositAccountPayload( + // val charges: List? = null, val adjustAdvanceTowardsFuturePayments: Boolean? = null, val allowWithdrawal: Boolean? = null, -// val charges: List? = null, val clientId: Int? = null, val dateFormat: String? = null, val depositPeriod: Int? = null, diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt index 7c25d67f59..2fa161c073 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/datamanager/DataManagerRecurringAccount.kt @@ -9,7 +9,6 @@ */ package com.mifos.core.network.datamanager -import com.mifos.core.datastore.UserPreferencesRepository import com.mifos.core.model.objects.payloads.RecurringDepositAccountPayload import com.mifos.core.network.BaseApiManager import com.mifos.room.entities.accounts.recurring.RecurringDeposit @@ -18,7 +17,6 @@ import kotlinx.coroutines.flow.Flow class DataManagerRecurringAccount( val mBaseApiManager: BaseApiManager, - private val prefManager: UserPreferencesRepository, ) { fun createRecurringDepositAccount( diff --git a/core/network/src/commonMain/kotlin/com/mifos/core/network/di/DataMangerModule.kt b/core/network/src/commonMain/kotlin/com/mifos/core/network/di/DataMangerModule.kt index 1fb5707637..1a90f3c8ed 100644 --- a/core/network/src/commonMain/kotlin/com/mifos/core/network/di/DataMangerModule.kt +++ b/core/network/src/commonMain/kotlin/com/mifos/core/network/di/DataMangerModule.kt @@ -24,6 +24,7 @@ import com.mifos.core.network.datamanager.DataManagerIdentifiers import com.mifos.core.network.datamanager.DataManagerLoan import com.mifos.core.network.datamanager.DataManagerNote import com.mifos.core.network.datamanager.DataManagerOffices +import com.mifos.core.network.datamanager.DataManagerRecurringAccount import com.mifos.core.network.datamanager.DataManagerRunReport import com.mifos.core.network.datamanager.DataManagerSavings import com.mifos.core.network.datamanager.DataManagerSearch @@ -52,5 +53,6 @@ val DataManagerModule = module { single { DataManagerStaff(get(), get(), get()) } single { DataManagerSurveys(get(), get(), get()) } single { DataManagerIdentifiers(get()) } + single { DataManagerRecurringAccount(get()) } single { DataManagerShare(get()) } } diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationRoute.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationRoute.kt index 25d86d0b01..e96b89390a 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationRoute.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationRoute.kt @@ -25,7 +25,7 @@ fun NavGraphBuilder.clientApplyNewApplicationRoute( onNavigateApplyLoanAccount: (Int) -> Unit, onNavigateApplySavingsAccount: (Int) -> Unit, onNavigateApplyShareAccount: (Int) -> Unit, - onNavigateApplyRecurringAccount: () -> Unit, + onNavigateApplyRecurringAccount: (Int) -> Unit, onNavigateApplyFixedAccount: () -> Unit, navController: NavController, ) { diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationsScreen.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationsScreen.kt index 65c25ce11d..530d602c16 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationsScreen.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientApplyNewApplications/ClientApplyNewApplicationsScreen.kt @@ -62,7 +62,7 @@ internal fun ClientApplyNewApplicationsScreen( onNavigateApplyLoanAccount: (Int) -> Unit, onNavigateApplySavingsAccount: (Int) -> Unit, onNavigateApplyShareAccount: (Int) -> Unit, - onNavigateApplyRecurringAccount: () -> Unit, + onNavigateApplyRecurringAccount: (Int) -> Unit, onNavigateApplyFixedAccount: () -> Unit, navController: NavController, viewModel: ClientApplyNewApplicationsViewModel = koinViewModel(), @@ -75,15 +75,9 @@ internal fun ClientApplyNewApplicationsScreen( is ClientApplyNewApplicationsEvent.OnActionClick -> { when (event.action) { ClientApplyNewApplicationsItem.NewFixedAccount -> onNavigateApplyFixedAccount() - ClientApplyNewApplicationsItem.NewLoanAccount -> onNavigateApplyLoanAccount( - state.clientId, - ) - - ClientApplyNewApplicationsItem.NewRecurringAccount -> onNavigateApplyRecurringAccount() - ClientApplyNewApplicationsItem.NewSavingsAccount -> onNavigateApplySavingsAccount( - state.clientId, - ) - + ClientApplyNewApplicationsItem.NewLoanAccount -> onNavigateApplyLoanAccount(state.clientId) + ClientApplyNewApplicationsItem.NewRecurringAccount -> onNavigateApplyRecurringAccount(state.clientId) + ClientApplyNewApplicationsItem.NewSavingsAccount -> onNavigateApplySavingsAccount(state.clientId) ClientApplyNewApplicationsItem.NewShareAccount -> onNavigateApplyShareAccount(state.clientId) } } diff --git a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt index 06b44b01f6..23f097298a 100644 --- a/feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt +++ b/feature/client/src/commonMain/kotlin/com/mifos/feature/client/navigation/ClientNavigation.kt @@ -345,7 +345,9 @@ fun NavGraphBuilder.clientNavGraph( createShareAccountDestination( navController = navController, ) - recurringAccountDestination() + recurringAccountDestination( + navController = navController, + ) fixedAccountDestination() } } diff --git a/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurringDeposit_string.xml b/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurringDeposit_string.xml new file mode 100644 index 0000000000..6270c0c6d2 --- /dev/null +++ b/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurringDeposit_string.xml @@ -0,0 +1,39 @@ + + + + Details + Terms + Settings + Interest + Charges + Create Recurring Deposit Account + Is Mandatory Deposit? + Adjust advance payments toward future installments? + Allow Withdrawals? + Lock-in Period + Frequency + Type + Recurring Deposit Details + Recurring Deposit Amount + Deposit Period + Deposit Frequency Same as Group/Center meeting + Minimum Deposit Term + And thereafter, in Multiples of + Maximum Deposit Term + For Pre-mature closure + Apply Penal Interest (less) + Penal Interest (%) + Period + Minimum Balance For Interest Calculation + Back + Next + No Internet Connection + \ No newline at end of file diff --git a/feature/recurringDeposit/src/commonMain/composeResources/values/string.xml b/feature/recurringDeposit/src/commonMain/composeResources/values/string.xml deleted file mode 100644 index 217caa04d9..0000000000 --- a/feature/recurringDeposit/src/commonMain/composeResources/values/string.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - Details - Terms - Settings - Interest - Charges - Create Recurring Deposit Account - \ No newline at end of file diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/di/RecurringDepositModule.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/di/RecurringDepositModule.kt new file mode 100644 index 0000000000..bf5e9f53fa --- /dev/null +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/di/RecurringDepositModule.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2025 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/android-client/blob/master/LICENSE.md + */ +package com.mifos.feature.recurringDeposit.di + +import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountViewModel +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module + +val RecurringDepositModule = module { + viewModelOf(::RecurringAccountViewModel) +} diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountRoute.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountRoute.kt index b3c674c7c3..cf3b9b764e 100644 --- a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountRoute.kt +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountRoute.kt @@ -15,19 +15,26 @@ import androidx.navigation.compose.composable import kotlinx.serialization.Serializable @Serializable -data object RecurringAccountRoute +data class RecurringAccountRoute( + val clientId: Int = -1, +) -fun NavGraphBuilder.recurringAccountDestination() { +fun NavGraphBuilder.recurringAccountDestination( + navController: NavController, +) { composable { RecurringAccountScreen( + navController = navController, onNavigateBack = {}, onFinish = {}, ) } } -fun NavController.navigateToRecurringAccountRoute() { +fun NavController.navigateToRecurringAccountRoute( + clientId: Int, +) { this.navigate( - RecurringAccountRoute, + RecurringAccountRoute(clientId = clientId), ) } diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt index 33abc8b65b..a829d59952 100644 --- a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt @@ -10,20 +10,25 @@ package com.mifos.feature.recurringDeposit.newRecurringDepositAccount import androidclient.feature.recurringdeposit.generated.resources.Res -import androidclient.feature.recurringdeposit.generated.resources.create_recurring_deposit_account -import androidclient.feature.recurringdeposit.generated.resources.step_charges -import androidclient.feature.recurringdeposit.generated.resources.step_details -import androidclient.feature.recurringdeposit.generated.resources.step_interest -import androidclient.feature.recurringdeposit.generated.resources.step_settings -import androidclient.feature.recurringdeposit.generated.resources.step_terms +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_create_recurring_deposit_account +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_charges +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_details +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_interest +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_settings +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_terms +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.component.MifosSweetError +import com.mifos.core.ui.components.MifosBreadcrumbNavBar +import com.mifos.core.ui.components.MifosProgressIndicator import com.mifos.core.ui.components.MifosStepper import com.mifos.core.ui.components.Step import com.mifos.core.ui.util.EventsEffect @@ -33,13 +38,15 @@ import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.Inter import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.SettingPage import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.TermsPage import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel @Composable internal fun RecurringAccountScreen( + navController: NavController, onNavigateBack: () -> Unit, onFinish: () -> Unit, modifier: Modifier = Modifier, - viewModel: RecurringAccountViewModel = viewModel(), + viewModel: RecurringAccountViewModel = koinViewModel(), ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -50,7 +57,10 @@ internal fun RecurringAccountScreen( } } + RecurringDepositAccountDialogBox(state = state) + RecurringAccountScaffold( + navController = navController, modifier = modifier, state = state, onAction = { viewModel.trySendAction(it) }, @@ -59,32 +69,34 @@ internal fun RecurringAccountScreen( @Composable private fun RecurringAccountScaffold( + navController: NavController, state: RecurringAccountState, modifier: Modifier = Modifier, onAction: (RecurringAccountAction) -> Unit, ) { val steps = listOf( - Step(name = stringResource(Res.string.step_details)) { + Step(name = stringResource(Res.string.feature_recurringDeposit_step_details)) { DetailsPage( onNext = { onAction(RecurringAccountAction.NextStep) }, ) }, - Step(name = stringResource(Res.string.step_terms)) { + Step(name = stringResource(Res.string.feature_recurringDeposit_step_terms)) { TermsPage( onNext = { onAction(RecurringAccountAction.NextStep) }, ) }, - Step(name = stringResource(Res.string.step_settings)) { + Step(name = stringResource(Res.string.feature_recurringDeposit_step_settings)) { SettingPage( - onNext = { onAction(RecurringAccountAction.NextStep) }, + state = state, + onAction = onAction, ) }, - Step(name = stringResource(Res.string.step_interest)) { + Step(name = stringResource(Res.string.feature_recurringDeposit_step_interest)) { InterestPage( onNext = { onAction(RecurringAccountAction.NextStep) }, ) }, - Step(name = stringResource(Res.string.step_charges)) { + Step(name = stringResource(Res.string.feature_recurringDeposit_step_charges)) { ChargesPage( onNext = { onAction(RecurringAccountAction.NextStep) }, ) @@ -92,21 +104,46 @@ private fun RecurringAccountScaffold( ) MifosScaffold( - title = stringResource(Res.string.create_recurring_deposit_account), + title = stringResource(Res.string.feature_recurringDeposit_create_recurring_deposit_account), onBackPressed = { onAction(RecurringAccountAction.NavigateBack) }, modifier = modifier, ) { paddingValues -> - if (state.dialogState == null) { - MifosStepper( - steps = steps, - currentIndex = state.currentStep, - onStepChange = { newIndex -> - onAction(RecurringAccountAction.OnStepChange(newIndex)) - }, - modifier = Modifier - .fillMaxWidth() - .padding(paddingValues), + Column( + modifier = Modifier.padding(paddingValues) + .fillMaxSize(), + ) { + MifosBreadcrumbNavBar(navController) + + if (state.screenState == null) { + MifosStepper( + steps = steps, + currentIndex = state.currentStep, + onStepChange = { newIndex -> + onAction(RecurringAccountAction.OnStepChange(newIndex)) + }, + modifier = Modifier + .fillMaxWidth() + .padding(paddingValues), + ) + } + } + } +} + +@Composable +fun RecurringDepositAccountDialogBox( + state: RecurringAccountState, +) { + when (state.screenState) { + is RecurringAccountState.ScreenState.Error -> { + MifosSweetError( + message = state.screenState.message, + isRetryEnabled = true, ) } + RecurringAccountState.ScreenState.Loading -> { + MifosProgressIndicator() + } + null -> {} } } diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt index ab427d5420..d21b490f0d 100644 --- a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountViewModel.kt @@ -9,22 +9,271 @@ */ package com.mifos.feature.recurringDeposit.newRecurringDepositAccount +import androidclient.feature.recurringdeposit.generated.resources.Res +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_no_internet_connection +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute +import com.mifos.core.common.utils.DataState +import com.mifos.core.data.repository.RecurringAccountRepository +import com.mifos.core.data.util.NetworkMonitor +import com.mifos.core.model.objects.payloads.RecurringDepositAccountPayload +import com.mifos.core.model.objects.template.recurring.FieldOfficerOption import com.mifos.core.ui.util.BaseViewModel +import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountState.ScreenState +import com.mifos.room.entities.templates.recurringDeposit.RecurringDepositAccountTemplate +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.getString -class RecurringAccountViewModel : BaseViewModel< +class RecurringAccountViewModel( + savedStateHandle: SavedStateHandle, + private val networkMonitor: NetworkMonitor, + private val recurringAccountRepository: RecurringAccountRepository, +) : BaseViewModel< RecurringAccountState, RecurringAccountEvent, RecurringAccountAction, >(RecurringAccountState()) { + init { + checkInitialConnectivity() + } + private val clientId = savedStateHandle.toRoute().clientId + + private fun setLoadingState() { + mutableStateFlow.update { + it.copy( + screenState = ScreenState.Loading, + ) + } + } + private fun setErrorState(message: String) { + mutableStateFlow.update { + it.copy( + screenState = ScreenState.Error(message), + ) + } + } + + private fun checkInitialConnectivity() { + viewModelScope.launch { + val isConnected = networkMonitor.isOnline.first() + mutableStateFlow.update { + it.copy(isOnline = isConnected) + } + if (isConnected) { +// loadTemplate() + // TODO + // Used only for testing purpose + // Must be removed from here after the implementation of detail screen is finished. + loadTemplateByProduct() + } else { + setErrorState( + getString(Res.string.feature_recurringDeposit_no_internet_connection), + ) + } + } + } + private fun handleRetry() { + mutableStateFlow.update { + it.copy( + screenState = null, + recurringDepositAccountTemplate = RecurringDepositAccountTemplate(), + ) + } + checkInitialConnectivity() + } + + private fun moveToNextStep() { + if (state.currentStep < state.totalSteps) { + mutableStateFlow.update { + it.copy( + currentStep = state.currentStep + 1, + ) + } + } else { + sendEvent(RecurringAccountEvent.Finish) + } + } + + private fun loadTemplate() { + viewModelScope.launch { + val online = networkMonitor.isOnline.first() + if (!online) { + setErrorState(getString(Res.string.feature_recurringDeposit_no_internet_connection)) + return@launch + } + recurringAccountRepository.getRecurringAccountTemplate().collect { templateState -> + when (templateState) { + is DataState.Error -> { + setErrorState(message = templateState.message) + } + is DataState.Loading -> { + setLoadingState() + } + is DataState.Success -> { + mutableStateFlow.update { + it.copy( + screenState = null, + recurringDepositAccountTemplate = templateState.data, + ) + } + } + } + } + } + } + private fun loadTemplateByProduct() { + viewModelScope.launch { + val online = networkMonitor.isOnline.first() + if (!online) { + setErrorState(getString(Res.string.feature_recurringDeposit_no_internet_connection)) + return@launch + } + recurringAccountRepository.getRecurringAccountTemplateByProduct( + clientId = clientId, + productId = state.recurringDepositAccountDetail.productId, + ).collect { templateState -> + when (templateState) { + is DataState.Error -> { + setErrorState(message = templateState.message) + } + is DataState.Loading -> { + setLoadingState() + } + is DataState.Success -> { + mutableStateFlow.update { + it.copy( + screenState = null, + recurringDepositAccountTemplate = templateState.data, + ) + } + } + } + } + } + } + + private fun formattedAmount(amount: String): String { + val currencySymbol = state.recurringDepositAccountTemplate.currency?.displaySymbol ?: "" + if (amount.isEmpty()) return "" + + val cleaned = amount.replace("[^0-9.-]".toRegex(), "") + + if (cleaned.isEmpty()) return "" + if (cleaned == "-") return "-" + if (cleaned == ".") return "." // Allows user to type just '.' + + val negative = cleaned.startsWith("-") + val magnitude = if (negative) cleaned.substring(1) else cleaned + + val parts = magnitude.split('.', limit = 2) + var intPart = parts[0].trimStart('0').ifEmpty { "0" } // Clean up leading zeros + + var decimalPart = "" + if (parts.size > 1) { + decimalPart = parts[1].take(2) + if (decimalPart.isNotEmpty() || parts[1].isEmpty()) { + decimalPart = ".$decimalPart" + } + } + + if (intPart == "0" && magnitude.startsWith('.') && decimalPart.isNotEmpty()) { + intPart = "" + } + + val numericPart = when { + amount.startsWith('.') && decimalPart.isNotEmpty() -> decimalPart + amount.startsWith("-.") && decimalPart.isNotEmpty() -> decimalPart + else -> "$intPart$decimalPart" + } + + val signedNumericPart = if (negative && numericPart.isNotEmpty()) "-$numericPart" else numericPart + + if (amount == "." && signedNumericPart.isEmpty()) return "." + + return "$currencySymbol$signedNumericPart" + } + + private fun createRecurringDepositAccount() { + viewModelScope.launch { + val s = state + val settings = s.recurringDepositAccountSettings + + val online = networkMonitor.isOnline.first() + if (!online) { + setErrorState(getString(Res.string.feature_recurringDeposit_no_internet_connection)) + return@launch + } + + val lockinFreq = settings.lockInPeriod.frequency.toIntOrNull() + val depositAmountInt = settings.recurringDepositDetails.depositAmount + .filter(Char::isDigit) + .toIntOrNull() + val recurringFreq = settings.minDepositTerm.frequency.toIntOrNull() + + val payload = RecurringDepositAccountPayload( + adjustAdvanceTowardsFuturePayments = settings.adjustAdvancePayments, + allowWithdrawal = settings.allowWithdrawals, + clientId = clientId, + dateFormat = "dd MMMM yyyy", + depositPeriod = settings.depositPeriod.period.toIntOrNull(), + depositPeriodFrequencyId = settings.depositPeriod.periodType, + expectedFirstDepositOnDate = null, + externalId = s.recurringDepositAccountDetail.externalId, + fieldOfficerId = s.recurringDepositAccountDetail.fieldOfficer?.id, + interestCalculationDaysInYearType = null, + interestCalculationType = null, + interestCompoundingPeriodType = null, + interestPostingPeriodType = null, + isCalendarInherited = null, + isMandatoryDeposit = settings.isMandatory, + locale = "en", + lockinPeriodFrequency = lockinFreq, + lockinPeriodFrequencyType = settings.lockInPeriod.frequencyTypeIndex, + mandatoryRecommendedDepositAmount = depositAmountInt, + monthDayFormat = "dd MMMM", + productId = s.recurringDepositAccountDetail.productId, + recurringFrequency = recurringFreq, + recurringFrequencyType = settings.minDepositTerm.frequencyTypeIndex, + // date in dd MM yyyy format. + submittedOnDate = s.recurringDepositAccountDetail.submittedOnDate, + ) + + if (state.isOnline) { + recurringAccountRepository.createRecurringDepositAccount(payload).collect { dataState -> + when (dataState) { + is DataState.Error -> { + if (depositAmountInt == null) { + setErrorState("Deposit amount is required") + } else if (lockinFreq == null) { + setErrorState("Lock-in period frequency is required") + } else if (recurringFreq == null) { + setErrorState("Recurring frequency is required") + } else { + setErrorState(dataState.message) + } + } + is DataState.Loading -> { + setLoadingState() + } + is DataState.Success -> { + mutableStateFlow.update { + it.copy(screenState = null) + } + } + } + } + } + } + } + override fun handleAction(action: RecurringAccountAction) { when (action) { RecurringAccountAction.NextStep -> { - mutableStateFlow.update { state -> - val maxIndex = 4 - state.copy(currentStep = (state.currentStep + 1).coerceAtMost(maxIndex)) - } + moveToNextStep() } is RecurringAccountAction.OnStepChange -> { @@ -38,33 +287,290 @@ class RecurringAccountViewModel : BaseViewModel< RecurringAccountAction.Finish -> { sendEvent(RecurringAccountEvent.Finish) } + + RecurringAccountAction.Retry -> { + handleRetry() + } + is RecurringAccountAction.RecurringAccountSettingsAction -> { + when (action) { + RecurringAccountAction.RecurringAccountSettingsAction.OnBackPress -> { + sendEvent(RecurringAccountEvent.NavigateBack) + } + RecurringAccountAction.RecurringAccountSettingsAction.OnNextPress -> { + moveToNextStep() + } + is RecurringAccountAction.RecurringAccountSettingsAction.SetDepositPeriod -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + depositPeriod = state.recurringDepositAccountSettings.depositPeriod.copy( + period = action.period, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetDepositPeriodType -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + depositPeriod = state.recurringDepositAccountSettings.depositPeriod.copy( + periodType = action.periodType, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetLockInPeriod -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + lockInPeriod = state.recurringDepositAccountSettings.lockInPeriod.copy( + frequency = action.frequency, + ), + ), + ) + } + } + is RecurringAccountAction.RecurringAccountSettingsAction.SetLockInPeriodType -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + lockInPeriod = state.recurringDepositAccountSettings.lockInPeriod.copy( + frequencyTypeIndex = action.frequencyTypeIndex, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreq -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + minDepositTerm = state.recurringDepositAccountSettings.minDepositTerm.copy( + frequency = action.frequency, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqType -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + minDepositTerm = state.recurringDepositAccountSettings.minDepositTerm.copy( + frequencyTypeIndex = action.frequencyTypeIndex, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqAfterInMultiOf -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + minDepositTerm = state.recurringDepositAccountSettings.minDepositTerm.copy( + frequencyAfterInMultiplesOf = action.frequencyAfterInMultiplesOf, + ), + ), + ) + } + } + is RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqTypeAfterInMultiOf -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + minDepositTerm = state.recurringDepositAccountSettings.minDepositTerm.copy( + frequencyTypeIndexAfterInMultiplesOf = action.frequencyTypeIndexAfterInMultiplesOf, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetMaxDepositTermFreq -> { + mutableStateFlow.update { state -> + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + maxDepositTerm = state.recurringDepositAccountSettings.maxDepositTerm.copy( + frequency = action.frequency, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetMaxDepositTermFreqType -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + maxDepositTerm = state.recurringDepositAccountSettings.maxDepositTerm.copy( + frequencyTypeIndex = action.frequencyTypeIndex, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosureInterestPeriodIndex -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + preMatureClosure = state.recurringDepositAccountSettings.preMatureClosure.copy( + interestPeriodIndex = action.interestPeriodIndex, + ), + ), + ) + } + } + + is RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosureMinimumBalanceForInterestCalculation -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + preMatureClosure = state.recurringDepositAccountSettings.preMatureClosure.copy( + minimumBalanceForInterestCalculation = formattedAmount(action.minimumBalanceForInterestCalculation), + ), + ), + ) + } + } + is RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosurePenalInterest -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + preMatureClosure = state.recurringDepositAccountSettings.preMatureClosure.copy( + penalInterest = action.penalInterest, + ), + ), + ) + } + } + is RecurringAccountAction.RecurringAccountSettingsAction.SetRecurringDepositAmount -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + recurringDepositDetails = state.recurringDepositAccountSettings + .recurringDepositDetails.copy( + depositAmount = formattedAmount(action.depositAmount), + ), + ), + ) + } + } + RecurringAccountAction.RecurringAccountSettingsAction.ToggleAdvancePaymentsTowardsFutureInstallments -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + adjustAdvancePayments = !state.recurringDepositAccountSettings.adjustAdvancePayments, + ), + ) + } + } + RecurringAccountAction.RecurringAccountSettingsAction.ToggleAllowWithdrawals -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + allowWithdrawals = !state.recurringDepositAccountSettings.allowWithdrawals, + ), + ) + } + } + RecurringAccountAction.RecurringAccountSettingsAction.ToggleDepositFrequencySameAsGroupCenterMeeting -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + depositPeriod = state.recurringDepositAccountSettings.depositPeriod.copy( + depositFrequencySameAsGroupCenterMeeting = !state.recurringDepositAccountSettings + .depositPeriod.depositFrequencySameAsGroupCenterMeeting, + ), + ), + ) + } + } + RecurringAccountAction.RecurringAccountSettingsAction.ToggleMandatoryDeposit -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + isMandatory = !state.recurringDepositAccountSettings.isMandatory, + ), + ) + } + } + RecurringAccountAction.RecurringAccountSettingsAction.TogglePreMatureClosureApplyPenalInterest -> { + mutableStateFlow.update { + state.copy( + recurringDepositAccountSettings = state.recurringDepositAccountSettings.copy( + preMatureClosure = state.recurringDepositAccountSettings.preMatureClosure.copy( + applyPenalInterest = !state.recurringDepositAccountSettings + .preMatureClosure.applyPenalInterest, + ), + ), + ) + } + } + } + } + + is RecurringAccountAction.RecurringAccountDetailsAction.SetProductId -> { + mutableStateFlow.update { + it.copy( + recurringDepositAccountDetail = it.recurringDepositAccountDetail.copy( + productId = action.productId, + ), + ) + } + loadTemplateByProduct() + } } } } data class RecurringAccountState( + val isOnline: Boolean = false, val currentStep: Int = 0, - val dialogState: Any? = null, - val currencyIndex: Int = -1, - val currencyError: String? = null, + val totalSteps: Int = 4, + val screenState: ScreenState? = null, + val recurringDepositAccountDetail: RecurringAccountDetailsState = RecurringAccountDetailsState(), + val recurringDepositAccountTemplate: RecurringDepositAccountTemplate = RecurringDepositAccountTemplate(), val recurringDepositAccountSettings: RecurringAccountSettingsState = RecurringAccountSettingsState(), +) { + sealed interface ScreenState { + data class Error(val message: String) : ScreenState + data object Loading : ScreenState + } +} +data class RecurringAccountDetailsState( + // TODO + // productId is set 6 only for testing. + // It should be set -1 when the implementation of detail screen is finished. + val productId: Int = 6, + val externalId: String = "", + val submittedOnDate: String = "", + val fieldOfficer: FieldOfficerOption? = null, ) data class RecurringAccountSettingsState( + val canDoNext: Boolean = false, val isMandatory: Boolean = false, val adjustAdvancePayments: Boolean = false, val allowWithdrawals: Boolean = false, val lockInPeriod: LockInPeriod = LockInPeriod(), val recurringDepositDetails: RecurringDepositDetails = RecurringDepositDetails(), val depositPeriod: DepositPeriod = DepositPeriod(), - val minimumDepositTerm: MinimumDepositTerm = MinimumDepositTerm(), + val minDepositTerm: MinDepositTerm = MinDepositTerm(), + val maxDepositTerm: MaxDepositTerm = MaxDepositTerm(), val preMatureClosure: PreMatureClosure = PreMatureClosure(), ) { data class LockInPeriod( val frequency: String = "", - val frequencyTypeIndex: Int = -1, - val freqTypeError: String? = null, + val frequencyTypeIndex: Int = 0, ) data class RecurringDepositDetails( @@ -73,25 +579,28 @@ data class RecurringAccountSettingsState( data class DepositPeriod( val period: String = "", - val periodType: Int = -1, - val periodTypeError: String? = null, + val periodType: Int = 0, val depositFrequencySameAsGroupCenterMeeting: Boolean = false, ) - data class MinimumDepositTerm( + data class MinDepositTerm( val frequency: String = "", - val frequencyTypeIndex: Int = -1, - val freqTypeError: String? = null, + val frequencyTypeIndex: Int = 0, + val frequencyTypeError: String? = null, val frequencyAfterInMultiplesOf: String = "", - val frequencyTypeIndexAfterInMultiplesOf: Int = -1, - val freqTypeAfterInMultiplesOfError: String? = null, + val frequencyTypeIndexAfterInMultiplesOf: Int = 0, + ) + + data class MaxDepositTerm( + val frequency: String = "", + val frequencyTypeIndex: Int = -1, + val frequencyTypeError: String? = null, ) data class PreMatureClosure( val applyPenalInterest: Boolean = false, val penalInterest: String = "", val interestPeriodIndex: Int = -1, - val interestPeriodIndexError: String? = null, val minimumBalanceForInterestCalculation: String = "", ) } @@ -101,6 +610,37 @@ sealed class RecurringAccountAction { data class OnStepChange(val index: Int) : RecurringAccountAction() object NavigateBack : RecurringAccountAction() object Finish : RecurringAccountAction() + data object Retry : RecurringAccountAction() + sealed class RecurringAccountDetailsAction : RecurringAccountAction() { + data class SetProductId(val productId: Int) : RecurringAccountDetailsAction() + } + + sealed class RecurringAccountSettingsAction : RecurringAccountAction() { + object ToggleMandatoryDeposit : RecurringAccountSettingsAction() + object ToggleAdvancePaymentsTowardsFutureInstallments : RecurringAccountSettingsAction() + object ToggleAllowWithdrawals : RecurringAccountSettingsAction() + data class SetLockInPeriod(val frequency: String) : RecurringAccountSettingsAction() + data class SetLockInPeriodType(val frequencyTypeIndex: Int) : RecurringAccountSettingsAction() + data class SetRecurringDepositAmount(val depositAmount: String) : RecurringAccountSettingsAction() + data class SetDepositPeriod(val period: String) : RecurringAccountSettingsAction() + data class SetDepositPeriodType(val periodType: Int) : RecurringAccountSettingsAction() + data object ToggleDepositFrequencySameAsGroupCenterMeeting : RecurringAccountSettingsAction() + + data class SetMinDepositTermFreq(val frequency: String) : RecurringAccountSettingsAction() + data class SetMinDepositTermFreqType(val frequencyTypeIndex: Int) : RecurringAccountSettingsAction() + data class SetMinDepositTermFreqAfterInMultiOf(val frequencyAfterInMultiplesOf: String) : RecurringAccountSettingsAction() + data class SetMinDepositTermFreqTypeAfterInMultiOf(val frequencyTypeIndexAfterInMultiplesOf: Int) : RecurringAccountSettingsAction() + + data class SetMaxDepositTermFreq(val frequency: String) : RecurringAccountSettingsAction() + data class SetMaxDepositTermFreqType(val frequencyTypeIndex: Int) : RecurringAccountSettingsAction() + data object TogglePreMatureClosureApplyPenalInterest : RecurringAccountSettingsAction() + data class SetPreMatureClosurePenalInterest(val penalInterest: String) : RecurringAccountSettingsAction() + data class SetPreMatureClosureInterestPeriodIndex(val interestPeriodIndex: Int) : RecurringAccountSettingsAction() + data class SetPreMatureClosureMinimumBalanceForInterestCalculation(val minimumBalanceForInterestCalculation: String) : RecurringAccountSettingsAction() + + data object OnBackPress : RecurringAccountSettingsAction() + data object OnNextPress : RecurringAccountSettingsAction() + } } sealed class RecurringAccountEvent { diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt index 365a938143..1159c65b65 100644 --- a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/SettingsPage.kt @@ -9,23 +9,351 @@ */ package com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages +import androidclient.feature.recurringdeposit.generated.resources.Res +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_adjust_advance_payments +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_allow_withdrawals +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_apply_penal_interest +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_back +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_deposit_frequency_same_as_meeting +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_deposit_period +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_for_pre_mature_closure +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_frequency +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_in_multiples_of +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_is_mandatory_deposit +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_lock_in_period +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_maximum_deposit_term +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_minimum_balance_for_interest +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_minimum_deposit_term +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_next +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_penal_interest_percentage +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_period +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_recurring_deposit_amount +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_recurring_deposit_details +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_step_settings +import androidclient.feature.recurringdeposit.generated.resources.feature_recurringDeposit_type +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Button +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mifos.core.designsystem.component.MifosOutlinedTextField +import com.mifos.core.designsystem.component.MifosTextFieldConfig +import com.mifos.core.designsystem.component.MifosTextFieldDropdown +import com.mifos.core.ui.components.MifosCheckBox +import com.mifos.core.ui.components.MifosTwoButtonRow +import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountAction +import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountState +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingPage(onNext: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text("Settings Page") - Spacer(Modifier.height(8.dp)) - Button(onClick = onNext) { - Text("Next Button") +fun SettingPage( + state: RecurringAccountState, + onAction: (RecurringAccountAction.RecurringAccountSettingsAction) -> Unit, +) { + val settingsState = state.recurringDepositAccountSettings + + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(scrollState), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + Text(stringResource(Res.string.feature_recurringDeposit_step_settings), fontWeight = FontWeight.Bold, fontSize = 18.sp) + + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(settingsState.isMandatory, onCheckedChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.ToggleMandatoryDeposit) }) + Text(stringResource(Res.string.feature_recurringDeposit_is_mandatory_deposit)) + } + Row(verticalAlignment = Alignment.CenterVertically) { + MifosCheckBox( + text = stringResource(Res.string.feature_recurringDeposit_adjust_advance_payments), + checked = settingsState.adjustAdvancePayments, + onCheckChanged = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.ToggleAdvancePaymentsTowardsFutureInstallments) }, + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + MifosCheckBox( + text = stringResource(Res.string.feature_recurringDeposit_allow_withdrawals), + checked = settingsState.allowWithdrawals, + onCheckChanged = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.ToggleAllowWithdrawals) }, + ) + } + } + + Text(stringResource(Res.string.feature_recurringDeposit_lock_in_period), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.lockInPeriod.frequency, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetLockInPeriod(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.lockInPeriod.frequencyTypeIndex != -1) { + state.recurringDepositAccountTemplate.lockinPeriodFrequencyTypeOptions?.get(settingsState.lockInPeriod.frequencyTypeIndex)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.lockinPeriodFrequencyTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction( + RecurringAccountAction.RecurringAccountSettingsAction.SetLockInPeriodType(id), + ) + }, + label = stringResource(Res.string.feature_recurringDeposit_type), + ) + Text(stringResource(Res.string.feature_recurringDeposit_recurring_deposit_details), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.recurringDepositDetails.depositAmount, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetRecurringDepositAmount(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_recurring_deposit_amount), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + Text(stringResource(Res.string.feature_recurringDeposit_deposit_period), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.depositPeriod.period, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetDepositPeriod(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_deposit_period), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.depositPeriod.periodType != -1) { + state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.get(settingsState.depositPeriod.periodType)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetDepositPeriodType(id)) + }, + label = stringResource(Res.string.feature_recurringDeposit_type), + modifier = Modifier.fillMaxWidth(), + ) + Row(verticalAlignment = Alignment.CenterVertically) { + MifosCheckBox( + text = stringResource(Res.string.feature_recurringDeposit_deposit_frequency_same_as_meeting), + checked = settingsState.depositPeriod.depositFrequencySameAsGroupCenterMeeting, + onCheckChanged = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.ToggleDepositFrequencySameAsGroupCenterMeeting) }, + ) + } + Text(stringResource(Res.string.feature_recurringDeposit_minimum_deposit_term), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.minDepositTerm.frequency, + onValueChange = { + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreq(it)) + }, + label = stringResource(Res.string.feature_recurringDeposit_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.minDepositTerm.frequencyTypeIndex != -1) { + state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.get(settingsState.minDepositTerm.frequencyTypeIndex)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqType(id)) + }, + label = stringResource(Res.string.feature_recurringDeposit_type), + modifier = Modifier.fillMaxWidth(), + ) + Text(stringResource(Res.string.feature_recurringDeposit_in_multiples_of), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.minDepositTerm.frequencyAfterInMultiplesOf, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqAfterInMultiOf(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.minDepositTerm.frequencyTypeIndexAfterInMultiplesOf != -1) { + state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.get(settingsState.minDepositTerm.frequencyTypeIndexAfterInMultiplesOf)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMinDepositTermFreqTypeAfterInMultiOf(id)) + }, + label = stringResource(Res.string.feature_recurringDeposit_type), + modifier = Modifier.fillMaxWidth(), + ) + Text(stringResource(Res.string.feature_recurringDeposit_maximum_deposit_term), fontWeight = FontWeight.Bold) + MifosOutlinedTextField( + value = settingsState.maxDepositTerm.frequency, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMaxDepositTermFreq(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_frequency), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.maxDepositTerm.frequencyTypeIndex != -1) { + state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.get(settingsState.maxDepositTerm.frequencyTypeIndex)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.periodFrequencyTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetMaxDepositTermFreqType(id)) + }, + label = stringResource(Res.string.feature_recurringDeposit_type), + modifier = Modifier.fillMaxWidth(), + ) + Text(stringResource(Res.string.feature_recurringDeposit_for_pre_mature_closure), fontWeight = FontWeight.Bold) + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + settingsState.preMatureClosure.applyPenalInterest, + onCheckedChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.TogglePreMatureClosureApplyPenalInterest) }, + ) + Text(stringResource(Res.string.feature_recurringDeposit_apply_penal_interest)) } + AnimatedVisibility( + visible = settingsState.preMatureClosure.applyPenalInterest, + ) { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + MifosOutlinedTextField( + value = settingsState.preMatureClosure.penalInterest, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosurePenalInterest(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_penal_interest_percentage), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + MifosTextFieldDropdown( + value = if (settingsState.preMatureClosure.interestPeriodIndex != -1) { + state.recurringDepositAccountTemplate.preClosurePenalInterestOnTypeOptions?.get(settingsState.preMatureClosure.interestPeriodIndex)?.value ?: "" + } else { + "" + }, + options = state.recurringDepositAccountTemplate.preClosurePenalInterestOnTypeOptions?.map { + it.value ?: "" + } ?: emptyList(), + onValueChanged = {}, + onOptionSelected = { id, name -> + onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosureInterestPeriodIndex(id)) + }, + label = stringResource(Res.string.feature_recurringDeposit_period), + modifier = Modifier.fillMaxWidth(), + ) + MifosOutlinedTextField( + value = settingsState.preMatureClosure.minimumBalanceForInterestCalculation, + onValueChange = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.SetPreMatureClosureMinimumBalanceForInterestCalculation(it)) }, + label = stringResource(Res.string.feature_recurringDeposit_minimum_balance_for_interest), + config = MifosTextFieldConfig( + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Next, + ), + ), + modifier = Modifier.fillMaxWidth(), + ) + } + } + + val isNextButtonActive = settingsState.preMatureClosure.penalInterest.isNotBlank() && + settingsState.preMatureClosure.minimumBalanceForInterestCalculation.isNotBlank() && + settingsState.recurringDepositDetails.depositAmount.isNotBlank() && + settingsState.depositPeriod.period.isNotBlank() && + settingsState.lockInPeriod.frequency.isNotBlank() && + settingsState.minDepositTerm.frequency.isNotBlank() && + settingsState.minDepositTerm.frequencyAfterInMultiplesOf.isNotBlank() && + settingsState.maxDepositTerm.frequency.isNotBlank() + + MifosTwoButtonRow( + firstBtnText = stringResource(Res.string.feature_recurringDeposit_back), + secondBtnText = stringResource(Res.string.feature_recurringDeposit_next), + onFirstBtnClick = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.OnBackPress) }, + onSecondBtnClick = { onAction(RecurringAccountAction.RecurringAccountSettingsAction.OnNextPress) }, + isButtonIconVisible = true, + isSecondButtonEnabled = isNextButtonActive, + ) } } + +@Preview +@Composable +private fun SettingPagePreview() { + SettingPage( + state = RecurringAccountState(), + onAction = {}, + ) +} diff --git a/version.txt b/version.txt index 91fd393753..656e604219 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2025.10.4-beta.0.6 \ No newline at end of file +2025.9.2-beta.0.59 \ No newline at end of file