Skip to content

Commit 4fccecb

Browse files
Feature Recurring Deposit Account Charges Step
1 parent 36fbf47 commit 4fccecb

File tree

6 files changed

+566
-13
lines changed

6 files changed

+566
-13
lines changed

core/ui/src/commonMain/composeResources/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,9 @@
118118
<string name="client_share_accounts_share_product">Share Product</string>
119119
<string name="client_share_accounts_pending_for_approval_shares">Pending For Approval Shares</string>
120120
<string name="client_share_accounts_approved_shares">Approved Shares</string>
121+
122+
123+
124+
125+
121126
</resources>

core/ui/src/commonMain/kotlin/com/mifos/core/ui/util/TextFieldsValidator.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ object TextFieldsValidator {
4545
}
4646

4747
fun doubleNumberValidator(input: String): StringResource? {
48+
val trimmedInput = input.trim()
4849
return when {
49-
input.isBlank() -> Res.string.error_field_empty
50-
input.count { it == '.' } > 1 -> Res.string.error_invalid_number
51-
input.any { !it.isDigit() && it != '.' } -> Res.string.error_digits_only
52-
input.toDoubleOrNull() == null -> Res.string.error_invalid_number
53-
input == "0" || input == "0.0" || input.toDoubleOrNull() == 0.0 -> Res.string.error_number_zero
50+
trimmedInput.isBlank() -> Res.string.error_field_empty
51+
trimmedInput.count { it == '.' } > 1 -> Res.string.error_invalid_number
52+
trimmedInput.any { !it.isDigit() && it != '.' } -> Res.string.error_digits_only
53+
trimmedInput.toDoubleOrNull() == null -> Res.string.error_invalid_number
54+
trimmedInput == "0" || trimmedInput == "0.0" || trimmedInput.toDoubleOrNull() == 0.0 -> Res.string.error_number_zero
5455
else -> null
5556
}
5657
}

feature/recurringDeposit/src/commonMain/composeResources/values/feature_recurring_deposit_string.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,11 @@
4949
<string name="feature_recurring_deposit_next_button">Next Button</string>
5050
<string name="feature_recurring_deposit_interest_page">Interest Page</string>
5151
<string name="feature_recurring_deposit_charges_page">Charges Page</string>
52+
<string name="recurring_step_charges_edit_charge">Edit Charge</string>
53+
<string name="recurring_step_charges_add_new">Add New</string>
54+
<string name="recurring_step_charges_add">Add</string>
55+
<string name="recurring_step_charges_view">View</string>
56+
<string name="recurring_step_charges_active">Active</string>
57+
<string name="recurring_step_charges_submit">Submit</string>
58+
5259
</resources>

feature/recurringDeposit/src/commonMain/kotlin/com/mifos/feature/recurringDeposit/newRecurringDepositAccount/RecurringAccountScreen.kt

Lines changed: 243 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,65 @@
1010
package com.mifos.feature.recurringDeposit.newRecurringDepositAccount
1111

1212
import 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
1315
import 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
1418
import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_charges
1519
import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_details
1620
import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_interest
1721
import androidclient.feature.recurringdeposit.generated.resources.feature_recurring_deposit_step_settings
1822
import 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
1928
import androidx.compose.foundation.layout.Column
2029
import androidx.compose.foundation.layout.fillMaxWidth
2130
import 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
2235
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.LaunchedEffect
2337
import 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
2442
import androidx.compose.ui.Modifier
2543
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2644
import androidx.navigation.NavController
45+
import com.mifos.core.common.utils.DateHelper
46+
import com.mifos.core.designsystem.component.MifosBottomSheet
2747
import 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
2853
import com.mifos.core.ui.components.MifosBreadcrumbNavBar
2954
import com.mifos.core.ui.components.MifosErrorComponent
3055
import com.mifos.core.ui.components.MifosProgressIndicator
3156
import com.mifos.core.ui.components.MifosStepper
57+
import com.mifos.core.ui.components.MifosTwoButtonRow
3258
import com.mifos.core.ui.components.Step
3359
import com.mifos.core.ui.util.EventsEffect
60+
import com.mifos.core.ui.util.TextFieldsValidator.doubleNumberValidator
3461
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.RecurringAccountAction.NavigateToStep
3562
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.ChargesPage
3663
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.DetailsPage
3764
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.InterestPage
3865
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.SettingPage
3966
import com.mifos.feature.recurringDeposit.newRecurringDepositAccount.pages.TermsPage
67+
import kotlinx.coroutines.delay
4068
import org.jetbrains.compose.resources.stringResource
4169
import org.koin.compose.viewmodel.koinViewModel
70+
import kotlin.time.Clock
71+
import kotlin.time.ExperimentalTime
4272

4373
@Composable
4474
internal 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

Comments
 (0)