1010package com.mifos.feature.recurringDeposit.newRecurringDepositAccount
1111
1212import androidclient.feature.recurringdeposit.generated.resources.Res
13+ import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_back
14+ import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_cancel
1315import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_create_recurring_deposit_account
16+ import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next
17+ import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_next_button
1418import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_charges
1519import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_details
1620import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_interest
1721import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_settings
1822import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_terms
23+ import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_add
24+ import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_add_new
25+ import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_edit_charge
26+ import androidclient.feature.recurringdeposit.generated.resources.recurring_step_charges_view
27+ import androidx.compose.foundation.layout.Arrangement
1928import androidx.compose.foundation.layout.Column
2029import androidx.compose.foundation.layout.fillMaxWidth
2130import androidx.compose.foundation.layout.padding
31+ import androidx.compose.foundation.lazy.LazyColumn
32+ import androidx.compose.foundation.lazy.itemsIndexed
33+ import androidx.compose.material3.SnackbarHostState
34+ import androidx.compose.material3.Text
2235import androidx.compose.runtime.Composable
36+ import androidx.compose.runtime.LaunchedEffect
2337import androidx.compose.runtime.getValue
38+ import androidx.compose.runtime.mutableStateOf
39+ import androidx.compose.runtime.remember
40+ import androidx.compose.runtime.saveable.rememberSaveable
41+ import androidx.compose.runtime.setValue
2442import androidx.compose.ui.Modifier
2543import androidx.lifecycle.compose.collectAsStateWithLifecycle
2644import androidx.navigation.NavController
45+ import com.mifos.core.common.utils.DateHelper
46+ import com.mifos.core.designsystem.component.MifosBottomSheet
2747import com.mifos.core.designsystem.component.MifosScaffold
48+ import com.mifos.core.designsystem.theme.DesignToken
49+ import com.mifos.core.designsystem.theme.MifosTypography
50+ import com.mifos.core.ui.components.Actions
51+ import com.mifos.core.ui.components.AddChargeBottomSheet
52+ import com.mifos.core.ui.components.MifosActionsChargeListingComponent
2853import com.mifos.core.ui.components.MifosBreadcrumbNavBar
2954import com.mifos.core.ui.components.MifosErrorComponent
3055import com.mifos.core.ui.components.MifosProgressIndicator
3156import com.mifos.core.ui.components.MifosStepper
57+ import com.mifos.core.ui.components.MifosTwoButtonRow
3258import com.mifos.core.ui.components.Step
3359import com.mifos.core.ui.util.EventsEffect
60+ import com.mifos.core.ui.util.TextFieldsValidator.doubleNumberValidator
3461import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountAction.NavigateToStep
3562import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.ChargesPage
3663import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.DetailsPage
3764import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.InterestPage
3865import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.SettingPage
3966import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.TermsPage
67+ import kotlinx.coroutines.delay
4068import org.jetbrains.compose.resources.stringResource
4169import org.koin.compose.viewmodel.koinViewModel
70+ import kotlin.time.Clock
71+ import kotlin.time.ExperimentalTime
4272
4373@Composable
4474internal fun RecurringAccountScreen (
@@ -55,14 +85,23 @@ internal fun RecurringAccountScreen(
5585 RecurringAccountEvent .NavigateBack -> onNavigateBack()
5686 RecurringAccountEvent .Finish -> onFinish()
5787 }
88+
5889 }
5990
91+
6092 RecurringAccountScaffold (
6193 navController = navController,
6294 modifier = modifier,
6395 state = state,
6496 onAction = { viewModel.trySendAction(it) },
6597 )
98+ val snackbarHostState = remember { SnackbarHostState () }
99+ NewRecurringAccountDialog (
100+ state = state,
101+ onAction = {viewModel.trySendAction(it)},
102+ snackbarHostState = snackbarHostState
103+
104+ )
66105}
67106
68107@Composable
@@ -97,7 +136,8 @@ private fun RecurringAccountScaffold(
97136 },
98137 Step (name = stringResource(Res .string.feature_recurring_deposit_step_charges)) {
99138 ChargesPage (
100- onNext = { onAction(RecurringAccountAction .OnNextPress ) },
139+ state = state,
140+ onAction = onAction,
101141 )
102142 },
103143 )
@@ -142,3 +182,205 @@ private fun RecurringAccountScaffold(
142182 }
143183 }
144184}
185+ @Composable
186+ private fun NewRecurringAccountDialog (
187+ state : RecurringAccountState ,
188+ onAction : (RecurringAccountAction ) -> Unit ,
189+ snackbarHostState : SnackbarHostState ,
190+ ) {
191+ when (state.dialogState) {
192+ is RecurringAccountState .DialogState .AddNewCharge -> AddNewChargeDialog (
193+ isEdit = state.dialogState.edit,
194+ state = state,
195+ onAction = onAction,
196+ index = state.dialogState.index,
197+ )
198+
199+ is RecurringAccountState .DialogState .showCharges -> ShowChargesDialog (
200+ state = state,
201+ onAction = onAction,
202+ )
203+
204+ is RecurringAccountState .DialogState .SuccessResponseStatus -> {
205+ LaunchedEffect (state.launchEffectKey) {
206+ snackbarHostState.showSnackbar(
207+ message = state.dialogState.msg,
208+ )
209+
210+ if (state.dialogState.successStatus) {
211+ delay(1000 )
212+ onAction(RecurringAccountAction .Finish )
213+ }
214+ }
215+ }
216+
217+ null -> Unit
218+ }
219+ }
220+ @OptIn(ExperimentalTime ::class , ExperimentalTime ::class )
221+ @Composable
222+ private fun AddNewChargeDialog (
223+ isEdit : Boolean ,
224+ index : Int = -1,
225+ state : RecurringAccountState ,
226+ onAction : (RecurringAccountAction ) -> Unit ,
227+ ) {
228+ var isAmountDirty by rememberSaveable { mutableStateOf(false ) }
229+
230+ LaunchedEffect (state.chargeAmount, isAmountDirty) {
231+ if (isAmountDirty) {
232+ if (state.chargeAmount.isNotEmpty()) {
233+ val amountError = doubleNumberValidator(state.chargeAmount)
234+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChargesAmountChangeError (amountError))
235+ } else {
236+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChargesAmountChangeError (null ))
237+ }
238+ }
239+ }
240+ fun isSelectableDate (utcTimeMillis : Long ): Boolean {
241+ return utcTimeMillis >= Clock .System .now().toEpochMilliseconds().minus(86_400_000L )
242+ }
243+ AddChargeBottomSheet (
244+ title = if (isEdit) {
245+ stringResource(Res .string.recurring_step_charges_edit_charge)
246+ } else {
247+ stringResource(Res .string.recurring_step_charges_add_new) + " " + stringResource(Res .string.feature_recurring_deposit_step_charges)
248+ },
249+ confirmText = if (isEdit) {
250+ stringResource(Res .string.recurring_step_charges_edit_charge)
251+ } else {
252+ stringResource(Res .string.recurring_step_charges_add)
253+ },
254+ dismissText = stringResource(Res .string.feature_recurring_deposit_cancel),
255+ showDatePicker = state.showChargesDatePick,
256+ selectedChargeName = if (state.chooseChargeIndex == - 1 ) {
257+ " "
258+ } else {
259+ state.template.chargeOptions?.getOrNull(state.chooseChargeIndex)?.name ? : " "
260+ },
261+ selectedDate = state.chargeDate,
262+ chargeAmount = state.chargeAmount,
263+ chargeType = if (state.chooseChargeIndex == - 1 ) {
264+ " "
265+ } else {
266+ state.template.chargeOptions?.get(state.chooseChargeIndex)?.chargeCalculationType?.value
267+ ? : " "
268+ },
269+ chargeCollectedOn = if (state.chooseChargeIndex == - 1 ) {
270+ " "
271+ } else {
272+ state.template.chargeOptions?.getOrNull(state.chooseChargeIndex)?.chargeTimeType?.value ? : " "
273+ },
274+ chargeOptions = state.template.chargeOptions?.map { it.name ? : " " } ? : emptyList(),
275+ onConfirm = {
276+ isAmountDirty = true
277+ if (state.chargeAmount.isNotEmpty() && state.chargeAmountError == null ) {
278+ if (isEdit) {
279+ onAction(RecurringAccountAction .RecurringAccountChargesAction .EditCharge (index))
280+ } else {
281+ onAction(RecurringAccountAction .RecurringAccountChargesAction .AddChargeToList )
282+ }
283+ }
284+ },
285+ onDismiss = { onAction(RecurringAccountAction .RecurringAccountChargesAction .DismissDialog ) },
286+ onChargeSelected = { index, _ ->
287+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChooseChargeIndex (index))
288+ },
289+ onDatePick = { show ->
290+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChargesDatePick (show))
291+ },
292+ onDateChange = { newDate ->
293+ if (isSelectableDate(newDate)) {
294+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChargesDateChange (
295+ DateHelper .getDateAsStringFromLong(newDate)))
296+ }
297+ },
298+ amountError = if (state.chargeAmountError != null ) stringResource(state.chargeAmountError) else null ,
299+ onAmountChange = { amount ->
300+ isAmountDirty = true
301+ onAction(RecurringAccountAction .RecurringAccountChargesAction .OnChargesAmountChange (amount))
302+ },
303+ )
304+ }
305+
306+
307+ @Composable
308+ private fun ShowChargesDialog (
309+ state : RecurringAccountState ,
310+ onAction : (RecurringAccountAction ) -> Unit ,
311+ ) {
312+ var expandedIndex: Int? by rememberSaveable { mutableStateOf(- 1 ) }
313+ MifosBottomSheet (
314+ onDismiss = {
315+ onAction(RecurringAccountAction .RecurringAccountChargesAction .DismissDialog )
316+ },
317+ content = {
318+ LazyColumn (
319+ modifier = Modifier .fillMaxWidth().padding(DesignToken .padding.large),
320+ verticalArrangement = Arrangement .spacedBy(DesignToken .padding.largeIncreased),
321+ ) {
322+ item {
323+ Text (
324+ text = stringResource(Res .string.recurring_step_charges_view) + " " + stringResource(
325+ Res .string.feature_recurring_deposit_step_charges
326+ ),
327+ style = MifosTypography .titleMediumEmphasized,
328+ )
329+ }
330+ itemsIndexed(items = state.addedCharges) { index, it ->
331+ MifosActionsChargeListingComponent (
332+ chargeTitle = it.name.toString(),
333+ type = it.type.toString(),
334+ date = it.date,
335+ collectedOn = it.collectedOn,
336+ amount = it.amount.toString(),
337+ onActionClicked = { action ->
338+ when (action) {
339+ is Actions .Delete -> {
340+ onAction(
341+ RecurringAccountAction .RecurringAccountChargesAction .DeleteChargeFromSelectedCharges (
342+ index
343+ )
344+ )
345+ }
346+
347+ is Actions .Edit -> {
348+ onAction(
349+ RecurringAccountAction .RecurringAccountChargesAction .EditChargeDialog (
350+ index
351+ )
352+ )
353+ }
354+
355+ else -> {}
356+ }
357+ },
358+
359+ isExpanded = expandedIndex == it.id,
360+ onExpandToggle = {
361+ expandedIndex = if (expandedIndex == it.id) - 1 else it.id
362+ },
363+ )
364+
365+
366+ }
367+ item {
368+ MifosTwoButtonRow (
369+ firstBtnText = stringResource(Res .string.feature_recurring_deposit_back),
370+ secondBtnText = stringResource(Res .string.recurring_step_charges_add_new),
371+ onFirstBtnClick = {
372+ onAction(RecurringAccountAction .RecurringAccountChargesAction .DismissDialog )
373+ },
374+ onSecondBtnClick = {
375+ onAction(RecurringAccountAction .RecurringAccountChargesAction .ShowAddChargeDialog )
376+ },
377+ )
378+ }
379+
380+
381+ }
382+
383+ }
384+ )
385+
386+ }
0 commit comments