diff --git a/core/ui/src/commonMain/composeResources/values/strings.xml b/core/ui/src/commonMain/composeResources/values/strings.xml index c2ba3e9797f..d523980df01 100644 --- a/core/ui/src/commonMain/composeResources/values/strings.xml +++ b/core/ui/src/commonMain/composeResources/values/strings.xml @@ -118,4 +118,9 @@ Share Product Pending For Approval Shares Approved Shares + + + + + \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt b/core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt index bee8680bb0b..94ffdb7fcd7 100644 --- a/core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt +++ b/core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt @@ -45,12 +45,13 @@ object TextFieldsValidator { } fun doubleNumberValidator(input: String): StringResource? { + val trimmedInput = input.trim() return when { - input.isBlank() -> Res.string.error_field_empty - input.count { it == '.' } > 1 -> Res.string.error_invalid_number - input.any { !it.isDigit() && it != '.' } -> Res.string.error_digits_only - input.toDoubleOrNull() == null -> Res.string.error_invalid_number - input == "0" || input == "0.0" || input.toDoubleOrNull() == 0.0 -> Res.string.error_number_zero + trimmedInput.isBlank() -> Res.string.error_field_empty + trimmedInput.count { it == '.' } > 1 -> Res.string.error_invalid_number + trimmedInput.any { !it.isDigit() && it != '.' } -> Res.string.error_digits_only + trimmedInput.toDoubleOrNull() == null -> Res.string.error_invalid_number + trimmedInput == "0" || trimmedInput == "0.0" || trimmedInput.toDoubleOrNull() == 0.0 -> Res.string.error_number_zero else -> null } } diff --git a/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml b/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml index b82f0de3f86..cfc1fcd1625 100644 --- a/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml +++ b/feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml @@ -49,4 +49,11 @@ Next Button Interest Page Charges Page + Edit Charge + Add New + Add + View + Active + Submit + \ No newline at end of file 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 6d9f1a85c86..cb531be3bd4 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,35 +10,65 @@ package com.mifos.feature.recurringDeposit.newRecurringDepositAccount import androidclient.feature.recurringdeposit.generated.resources.Res +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_back +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_cancel import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_create_recurring_deposit_account +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next_button import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_charges import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_details import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_interest import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_settings import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_terms +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_add +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_add_new +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_edit_charge +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_view +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController +import com.mifos.core.common.utils.DateHelper +import com.mifos.core.designsystem.component.MifosBottomSheet import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.theme.DesignToken +import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.ui.components.Actions +import com.mifos.core.ui.components.AddChargeBottomSheet +import com.mifos.core.ui.components.MifosActionsChargeListingComponent import com.mifos.core.ui.components.MifosBreadcrumbNavBar import com.mifos.core.ui.components.MifosErrorComponent import com.mifos.core.ui.components.MifosProgressIndicator import com.mifos.core.ui.components.MifosStepper +import com.mifos.core.ui.components.MifosTwoButtonRow import com.mifos.core.ui.components.Step import com.mifos.core.ui.util.EventsEffect +import com.mifos.core.ui.util.TextFieldsValidator.doubleNumberValidator import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountAction.NavigateToStep import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.ChargesPage import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.DetailsPage import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.InterestPage import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.SettingPage import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.TermsPage +import kotlinx.coroutines.delay import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel +import kotlin.time.Clock +import kotlin.time.ExperimentalTime @Composable internal fun RecurringAccountScreen( @@ -55,14 +85,23 @@ internal fun RecurringAccountScreen( RecurringAccountEvent.NavigateBack -> onNavigateBack() RecurringAccountEvent.Finish -> onFinish() } + } + RecurringAccountScaffold( navController = navController, modifier = modifier, state = state, onAction = { viewModel.trySendAction(it) }, ) + val snackbarHostState = remember { SnackbarHostState() } + NewRecurringAccountDialog( + state = state, + onAction = {viewModel.trySendAction(it)}, + snackbarHostState = snackbarHostState + + ) } @Composable @@ -97,7 +136,8 @@ private fun RecurringAccountScaffold( }, Step(name = stringResource(Res.string.feature_recurring_deposit_step_charges)) { ChargesPage( - onNext = { onAction(RecurringAccountAction.OnNextPress) }, + state = state, + onAction = onAction, ) }, ) @@ -142,3 +182,205 @@ private fun RecurringAccountScaffold( } } } +@Composable +private fun NewRecurringAccountDialog( + state: RecurringAccountState, + onAction: (RecurringAccountAction) -> Unit, + snackbarHostState: SnackbarHostState, +) { + when (state.dialogState) { + is RecurringAccountState.DialogState.AddNewCharge -> AddNewChargeDialog( + isEdit = state.dialogState.edit, + state = state, + onAction = onAction, + index = state.dialogState.index, + ) + + is RecurringAccountState.DialogState.showCharges -> ShowChargesDialog( + state = state, + onAction = onAction, + ) + + is RecurringAccountState.DialogState.SuccessResponseStatus -> { + LaunchedEffect(state.launchEffectKey) { + snackbarHostState.showSnackbar( + message = state.dialogState.msg, + ) + + if (state.dialogState.successStatus) { + delay(1000) + onAction(RecurringAccountAction.Finish) + } + } + } + + null -> Unit + } +} +@OptIn(ExperimentalTime::class, ExperimentalTime::class) +@Composable +private fun AddNewChargeDialog( + isEdit: Boolean, + index: Int = -1, + state: RecurringAccountState, + onAction: (RecurringAccountAction) -> Unit, +) { + var isAmountDirty by rememberSaveable { mutableStateOf(false) } + + LaunchedEffect(state.chargeAmount, isAmountDirty) { + if (isAmountDirty) { + if (state.chargeAmount.isNotEmpty()) { + val amountError = doubleNumberValidator(state.chargeAmount) + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChangeError(amountError)) + } else { + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChangeError(null)) + } + } + } + fun isSelectableDate(utcTimeMillis: Long): Boolean { + return utcTimeMillis >= Clock.System.now().toEpochMilliseconds().minus(86_400_000L) + } + AddChargeBottomSheet( + title = if (isEdit) { + stringResource(Res.string.recurring_step_charges_edit_charge) + } else { + stringResource(Res.string.recurring_step_charges_add_new) + " " + stringResource(Res.string.feature_recurring_deposit_step_charges) + }, + confirmText = if (isEdit) { + stringResource(Res.string.recurring_step_charges_edit_charge) + } else { + stringResource(Res.string.recurring_step_charges_add) + }, + dismissText = stringResource(Res.string.feature_recurring_deposit_cancel), + showDatePicker = state.showChargesDatePick, + selectedChargeName = if (state.chooseChargeIndex == -1) { + "" + } else { + state.template.chargeOptions?.getOrNull(state.chooseChargeIndex)?.name ?: "" + }, + selectedDate = state.chargeDate, + chargeAmount = state.chargeAmount, + chargeType = if (state.chooseChargeIndex == -1) { + "" + } else { + state.template.chargeOptions?.get(state.chooseChargeIndex)?.chargeCalculationType?.value + ?: "" + }, + chargeCollectedOn = if (state.chooseChargeIndex == -1) { + "" + } else { + state.template.chargeOptions?.getOrNull(state.chooseChargeIndex)?.chargeTimeType?.value ?: "" + }, + chargeOptions = state.template.chargeOptions?.map { it.name ?: "" } ?: emptyList(), + onConfirm = { + isAmountDirty = true + if (state.chargeAmount.isNotEmpty() && state.chargeAmountError == null) { + if (isEdit) { + onAction(RecurringAccountAction.RecurringAccountChargesAction.EditCharge(index)) + } else { + onAction(RecurringAccountAction.RecurringAccountChargesAction.AddChargeToList) + } + } + }, + onDismiss = { onAction(RecurringAccountAction.RecurringAccountChargesAction.DismissDialog) }, + onChargeSelected = { index, _ -> + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChooseChargeIndex(index)) + }, + onDatePick = { show -> + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChargesDatePick(show)) + }, + onDateChange = { newDate -> + if (isSelectableDate(newDate)) { + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChargesDateChange( + DateHelper.getDateAsStringFromLong(newDate))) + } + }, + amountError = if (state.chargeAmountError != null) stringResource(state.chargeAmountError) else null, + onAmountChange = { amount -> + isAmountDirty = true + onAction(RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChange(amount)) + }, + ) +} + + +@Composable +private fun ShowChargesDialog( + state: RecurringAccountState, + onAction: (RecurringAccountAction) -> Unit, +) { + var expandedIndex: Int? by rememberSaveable { mutableStateOf(-1) } + MifosBottomSheet( + onDismiss = { + onAction(RecurringAccountAction.RecurringAccountChargesAction.DismissDialog) + }, + content = { + LazyColumn( + modifier = Modifier.fillMaxWidth().padding(DesignToken.padding.large), + verticalArrangement = Arrangement.spacedBy(DesignToken.padding.largeIncreased), + ) { + item { + Text( + text = stringResource(Res.string.recurring_step_charges_view) + " " + stringResource( + Res.string.feature_recurring_deposit_step_charges + ), + style = MifosTypography.titleMediumEmphasized, + ) + } + itemsIndexed(items = state.addedCharges) { index, it -> + MifosActionsChargeListingComponent( + chargeTitle = it.name.toString(), + type = it.type.toString(), + date = it.date, + collectedOn = it.collectedOn, + amount = it.amount.toString(), + onActionClicked = { action -> + when (action) { + is Actions.Delete -> { + onAction( + RecurringAccountAction.RecurringAccountChargesAction.DeleteChargeFromSelectedCharges( + index + ) + ) + } + + is Actions.Edit -> { + onAction( + RecurringAccountAction.RecurringAccountChargesAction.EditChargeDialog( + index + ) + ) + } + + else -> {} + } + }, + + isExpanded = expandedIndex == it.id, + onExpandToggle = { + expandedIndex = if (expandedIndex == it.id) -1 else it.id + }, + ) + + + } + item { + MifosTwoButtonRow( + firstBtnText = stringResource(Res.string.feature_recurring_deposit_back), + secondBtnText = stringResource(Res.string.recurring_step_charges_add_new), + onFirstBtnClick = { + onAction(RecurringAccountAction.RecurringAccountChargesAction.DismissDialog) + }, + onSecondBtnClick = { + onAction(RecurringAccountAction.RecurringAccountChargesAction.ShowAddChargeDialog) + }, + ) + } + + + } + + } + ) + +} 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 d477224f7f4..426124110e5 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 @@ -18,6 +18,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.mifos.core.common.utils.DataState +import com.mifos.core.common.utils.DateHelper import com.mifos.core.data.repository.RecurringAccountRepository import com.mifos.core.data.util.NetworkMonitor import com.mifos.core.model.objects.payloads.RecurringDepositAccountPayload @@ -28,7 +29,10 @@ import com.mifos.room.entities.templates.recurringDeposit.RecurringDepositAccoun import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.getString +import kotlin.time.Clock +import kotlin.time.ExperimentalTime const val TOTAL_STEPS = 4 @@ -251,6 +255,140 @@ class RecurringAccountViewModel( } } } + private fun handleChargesAmountError (error: StringResource?){ + mutableStateFlow.update { + it.copy( + chargeAmountError = error + ) + } + } + private fun handleEditChargeDialog(index : Int){ + val selectedEditCharge = state.addedCharges[index] + val chooseChargeIndex = state.template.chargeOptions?.indexOfFirst { + it.id == selectedEditCharge.id + }?: -1 + mutableStateFlow.update { + it.copy( + chargeAmount = selectedEditCharge.amount.toString(), + chargeDate = selectedEditCharge.date, + chooseChargeIndex = chooseChargeIndex, + dialogState = RecurringAccountState.DialogState.AddNewCharge(true,index) + + ) + } + } + private fun handleDeleteCharge(index : Int){ + val newCharges = state.addedCharges.toMutableList().apply { + removeAt(index) + } + mutableStateFlow.update{ + it.copy(addedCharges = newCharges) + } + } + private fun handleChargesDatePick(action : RecurringAccountAction.RecurringAccountChargesAction.OnChargesDatePick){ + mutableStateFlow.update { + it.copy( + showChargesDatePick = action.state + ) + } + } + private fun handleChooseChargeIndexChange(action : RecurringAccountAction.RecurringAccountChargesAction.OnChooseChargeIndex){ + mutableStateFlow.update { + it.copy( + chooseChargeIndex = action.index + ) + } + } + private fun handleChargesDateChange(action: RecurringAccountAction.RecurringAccountChargesAction.OnChargesDateChange){ + mutableStateFlow.update { + it.copy( + chargeDate = action.date + ) + } + } + private fun handleChargesAmountChange(action: RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChange){ + mutableStateFlow.update { + it.copy( + chargeAmount = action.amount + ) + } + } + private fun handleDismissDialog(){ + mutableStateFlow.update { + it.copy( + dialogState =null + ) + } + } + private fun handleAddToChargeList(){ + val selectedIndex = state.chooseChargeIndex + val selectedCharges = state.template?.chargeOptions?.getOrNull(selectedIndex) + val amount = state.chargeAmount.toDoubleOrNull()?:selectedCharges?.amount?:0.0 + if (selectedCharges != null && state.chargeAmountError == null){ + val newCharge = CreatedCharges( + id = selectedCharges.id, + name = selectedCharges.name, + amount = amount, + date = state.chargeDate, + type = selectedCharges.chargeCalculationType?.value ?: "", + collectedOn = selectedCharges.chargeTimeType?.value ?: "", + ) + mutableStateFlow.update { + it.copy( + addedCharges = it.addedCharges + newCharge, + chooseChargeIndex = -1, + dialogState = null, + chargeAmount = "", + ) + } + } else { + mutableStateFlow.update { + it.copy( + chooseChargeIndex = -1, + dialogState = null, + chargeAmount = "", + ) + } + } + } + + private fun handleEditCharge(index: Int) { + val selectedIndex = state.chooseChargeIndex + val selectedCharge = state.template.chargeOptions?.getOrNull(selectedIndex) + val amount = state.chargeAmount.toDoubleOrNull() ?: selectedCharge?.amount ?: 0.0 + if (selectedCharge != null && state.chargeAmountError == null) { + val newCharge = CreatedCharges( + id = selectedCharge.id, + name = selectedCharge.name, + amount = amount, + date = state.chargeDate, + type = selectedCharge.chargeCalculationType?.value ?: "", + collectedOn = selectedCharge.chargeTimeType?.value ?: "", + ) + val currentAddedCharges = state.addedCharges.toMutableList() + currentAddedCharges[index] = newCharge + mutableStateFlow.update { + it.copy( + addedCharges = currentAddedCharges, + chooseChargeIndex = -1, + dialogState = RecurringAccountState.DialogState.showCharges, + chargeAmount = "", + ) + } + } + } + private fun handleShowAddChargeDialog() { + mutableStateFlow.update { + it.copy(dialogState = RecurringAccountState.DialogState.AddNewCharge(false)) + } + } + + private fun handleShowChargeDialog() { + mutableStateFlow.update { + it.copy(dialogState = RecurringAccountState.DialogState.showCharges) + } + } + private fun loadRecurringAccountTemplateWithProduct( clientId: Int, @@ -577,11 +715,28 @@ class RecurringAccountViewModel( RecurringAccountAction.OnNextPress -> { moveToNextStep() } + + RecurringAccountAction.RecurringAccountChargesAction.AddChargeToList -> handleAddToChargeList() + is RecurringAccountAction.RecurringAccountChargesAction.DeleteChargeFromSelectedCharges -> handleDeleteCharge(action.index) + RecurringAccountAction.RecurringAccountChargesAction.DismissDialog -> handleDismissDialog() + is RecurringAccountAction.RecurringAccountChargesAction.EditCharge -> handleEditCharge(action.index) + is RecurringAccountAction.RecurringAccountChargesAction.EditChargeDialog -> handleEditChargeDialog(action.index) + is RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChange -> handleChargesAmountChange(action) + is RecurringAccountAction.RecurringAccountChargesAction.OnChargesAmountChangeError -> handleChargesAmountError(action.error) + is RecurringAccountAction.RecurringAccountChargesAction.OnChargesDateChange -> handleChargesDateChange(action) + is RecurringAccountAction.RecurringAccountChargesAction.OnChargesDatePick -> handleChargesDatePick(action) + is RecurringAccountAction.RecurringAccountChargesAction.OnChooseChargeIndex -> handleChooseChargeIndexChange(action) + RecurringAccountAction.RecurringAccountChargesAction.ShowAddChargeDialog -> handleShowAddChargeDialog() + RecurringAccountAction.RecurringAccountChargesAction.ShowCharge -> handleShowChargeDialog() + RecurringAccountAction.Finish -> { + sendEvent(RecurringAccountEvent.Finish) + } + } } } -data class RecurringAccountState( +data class RecurringAccountState @OptIn(ExperimentalTime::class) constructor( val isOnline: Boolean = false, val clientId: Int = -1, val currentStep: Int = 0, @@ -590,15 +745,40 @@ data class RecurringAccountState( val recurringDepositAccountDetail: RecurringAccountDetailsState = RecurringAccountDetailsState(), val template: RecurringDepositAccountTemplate = RecurringDepositAccountTemplate(), val recurringDepositAccountSettings: RecurringAccountSettingsState = RecurringAccountSettingsState(), + val chargeAmountError : StringResource? = null, + val chargeAmount : String = " ", + val recurringDepositAccountCharges : RecurringAccountChargesState = RecurringAccountChargesState(), + val dialogState: DialogState? = null, val currencyIndex: Int = -1, val currencyError: String? = null, + val addedCharges : List = emptyList(), + val chooseChargeIndex : Int = -1, + val showChargesDatePick : Boolean = false, + val launchEffectKey: Int? = null, + val chargeDate: String = DateHelper.getDateAsStringFromLong( + Clock.System.now().toEpochMilliseconds(), + ), ) { sealed interface ScreenState { data class Error(val message: String) : ScreenState data object Loading : ScreenState data object Success : ScreenState } + sealed interface DialogState { + data object showCharges : DialogState + data class AddNewCharge (val edit : Boolean, val index : Int = -1) : DialogState + data class SuccessResponseStatus(val successStatus: Boolean, val msg: String = "") : + DialogState + } } +data class RecurringAccountChargesState @OptIn(ExperimentalTime::class) constructor( + val chooseChargeIndex : Int = -1 , + + + + + + ) data class RecurringAccountDetailsState( val productId: Int = -1, @@ -679,12 +859,34 @@ data class RecurringAccountSettingsState( maxDepositTerm.frequency.isNotBlank() } + sealed class RecurringAccountAction { data class NavigateToStep(val index: Int) : RecurringAccountAction() object NavigateBack : RecurringAccountAction() object OnBackPress : RecurringAccountAction() object OnNextPress : RecurringAccountAction() data object Retry : RecurringAccountAction() + data object Finish : RecurringAccountAction() + + + sealed class RecurringAccountChargesAction : RecurringAccountAction(){ + object ShowAddChargeDialog : RecurringAccountAction() + object ShowCharge : RecurringAccountAction() + data class EditCharge (val index : Int ) : RecurringAccountAction() + data class OnChooseChargeIndex(val index : Int) : RecurringAccountAction() + data class OnChargesDatePick (val state : Boolean): RecurringAccountAction() + data class OnChargesDateChange(val date : String): RecurringAccountAction() + data class OnChargesAmountChange(val amount : String): RecurringAccountAction() + data class OnChargesAmountChangeError (val error : StringResource?): RecurringAccountAction() + object AddChargeToList : RecurringAccountAction() + object DismissDialog : RecurringAccountAction() + data class DeleteChargeFromSelectedCharges (val index : Int): RecurringAccountAction() + data class EditChargeDialog(val index : Int): RecurringAccountAction() + + } + + + sealed class RecurringAccountDetailsAction : RecurringAccountAction() { data class OnProductNameChange(val index: Int) : RecurringAccountDetailsAction() @@ -694,6 +896,7 @@ sealed class RecurringAccountAction { data class OnExternalIdChange(val value: String) : RecurringAccountDetailsAction() } + sealed class RecurringAccountSettingsAction : RecurringAccountAction() { object ToggleMandatoryDeposit : RecurringAccountSettingsAction() object ToggleAdvancePaymentsTowardsFutureInstallments : RecurringAccountSettingsAction() @@ -723,3 +926,13 @@ sealed class RecurringAccountEvent { object NavigateBack : RecurringAccountEvent() object Finish : RecurringAccountEvent() } + + +data class CreatedCharges( + val id: Int? = -1, + val name: String?, + val date: String, + val type: String?, + val amount: Double? = 0.0, + val collectedOn: String = "", +) \ No newline at end of file diff --git a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/ChargesPage.kt b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/ChargesPage.kt index cacdd0e1248..d2d51629cde 100644 --- a/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/ChargesPage.kt +++ b/feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/pages/ChargesPage.kt @@ -10,26 +10,111 @@ package com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages import androidclient.feature.recurringdeposit.generated.resources.Res +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_back import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_charges_page +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next_button +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_charges +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_settings +import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_submitted_on +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_active +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_add_new +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_submit +import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_view +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import com.mifos.core.designsystem.icon.MifosIcons +import com.mifos.core.designsystem.theme.DesignToken +import com.mifos.core.designsystem.theme.MifosTypography +import com.mifos.core.ui.components.MifosRowWithTextAndButton +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 @Composable -fun ChargesPage(onNext: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(stringResource(Res.string.feature_recurring_deposit_charges_page)) - Spacer(Modifier.height(8.dp)) - Button(onClick = onNext) { - Text(stringResource(Res.string.feature_recurring_deposit_next_button)) +fun ChargesPage( + state : RecurringAccountState, + onAction : (RecurringAccountAction) -> Unit, + +) { + Column(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()), + ) { + Text( + stringResource(Res.string.feature_recurring_deposit_charges_page), + style = MifosTypography.labelLargeEmphasized, + ) + Spacer(Modifier.height(DesignToken.padding.large)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, + ) { + Row( + modifier = Modifier.clickable { + onAction(RecurringAccountAction.RecurringAccountChargesAction.ShowAddChargeDialog) + }, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = MifosIcons.Add, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(DesignToken.sizes.iconSmall), + ) + + Text( + text = stringResource(Res.string.recurring_step_charges_add_new), + color = MaterialTheme.colorScheme.primary, + style = MifosTypography.labelLargeEmphasized, + ) + } + } + + Spacer(Modifier.height(DesignToken.padding.large)) + + MifosRowWithTextAndButton( + onBtnClick = { + onAction(RecurringAccountAction.RecurringAccountChargesAction.ShowCharge) + }, + btnText = stringResource(Res.string.recurring_step_charges_view), + text = state.addedCharges.size.toString() + " " + stringResource(Res.string.recurring_step_charges_active) + " " + stringResource( + Res.string.feature_recurring_deposit_step_charges), + btnEnabled = state.addedCharges.isNotEmpty(), + ) } + MifosTwoButtonRow( + firstBtnText = stringResource(Res.string.feature_recurring_deposit_back), + secondBtnText = stringResource(Res.string.recurring_step_charges_submit), + onFirstBtnClick = { + onAction(RecurringAccountAction.OnBackPress) + }, + onSecondBtnClick = { + onAction(RecurringAccountAction.Finish) + }, + modifier = Modifier.padding(top = DesignToken.padding.small), + ) + + } }