Skip to content

Commit 795b533

Browse files
refactor: loan repayment schedule with prepopulated data (#2950)
1 parent d53339d commit 795b533

File tree

10 files changed

+169
-61
lines changed

10 files changed

+169
-61
lines changed

core/model/src/commonMain/kotlin/org/mifos/mobile/core/model/entity/AccountDetails.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.mifos.mobile.core.model.enums.TransferType
1717
@Serializable
1818
data class AccountDetails(
1919
val accountId: Long,
20+
val accountNo: String = "",
2021
val outstandingBalance: Double? = null,
2122
val transferType: String,
2223
val transferTarget: TransferType,

feature/loan-account/src/commonMain/composeResources/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@
105105
<string name="feature_loan_installments_paid_label">Installments Paid</string>
106106
<string name="feature_loan_installments_left_label">Installments Left</string>
107107
<string name="feature_loan_total_installments_label">Total Installments</string>
108-
108+
<string name="feature_loan_installment_number">%1$s Installment</string>
109+
<string name="feature_loan_repayment_pay">Pay %1$s</string>
109110
<string name="feature_loan_paid">Paid</string>
110111
<string name="feature_loan_due">Due</string>
111112
<string name="feature_loan_pay">Pay</string>

feature/loan-account/src/commonMain/kotlin/org/mifos/mobile/feature/loanaccount/component/RepaymentPeriodCard.kt

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ import androidx.compose.foundation.layout.Column
1717
import androidx.compose.foundation.layout.Row
1818
import androidx.compose.foundation.layout.Spacer
1919
import androidx.compose.foundation.layout.fillMaxWidth
20+
import androidx.compose.foundation.layout.height
2021
import androidx.compose.foundation.layout.padding
2122
import androidx.compose.foundation.layout.size
2223
import androidx.compose.foundation.layout.width
24+
import androidx.compose.foundation.layout.wrapContentWidth
2325
import androidx.compose.foundation.shape.CircleShape
24-
import androidx.compose.material3.ButtonDefaults
2526
import androidx.compose.material3.MaterialTheme
2627
import androidx.compose.material3.Text
2728
import androidx.compose.runtime.Composable
@@ -30,10 +31,12 @@ import androidx.compose.ui.Modifier
3031
import androidx.compose.ui.draw.clip
3132
import androidx.compose.ui.graphics.Color
3233
import androidx.compose.ui.unit.dp
34+
import kotlinx.datetime.Clock
3335
import mifos_mobile.feature.loan_account.generated.resources.Res
3436
import mifos_mobile.feature.loan_account.generated.resources.feature_loan_due
37+
import mifos_mobile.feature.loan_account.generated.resources.feature_loan_installment_number
3538
import mifos_mobile.feature.loan_account.generated.resources.feature_loan_paid
36-
import mifos_mobile.feature.loan_account.generated.resources.feature_loan_pay
39+
import mifos_mobile.feature.loan_account.generated.resources.feature_loan_repayment_pay
3740
import org.jetbrains.compose.resources.stringResource
3841
import org.mifos.mobile.core.common.CurrencyFormatter
3942
import org.mifos.mobile.core.common.DateHelper
@@ -54,9 +57,13 @@ fun RepaymentScheduleItem(
5457
onPayClick: () -> Unit = {},
5558
) {
5659
val isPaid = period.complete == true
60+
val dueDateMillis = DateHelper.getDateAsLongFromList(period.dueDate)
5761
val dueDate = DateHelper.getDateAsString(period.dueDate)
5862
val amount = CurrencyFormatter.format(period.totalDueForPeriod, currencyCode, maxDigits)
5963

64+
val todayMillis = Clock.System.now().toEpochMilliseconds()
65+
val canPay = !isPaid && (dueDateMillis?.let { it <= todayMillis } ?: false)
66+
6067
MifosCustomCard(
6168
variant = CardVariant.OUTLINED,
6269
modifier = modifier
@@ -82,7 +89,7 @@ fun RepaymentScheduleItem(
8289
contentAlignment = Alignment.Center,
8390
) {
8491
Text(
85-
text = period.period?.toString()?.padStart(2, '0') ?: "--",
92+
text = period.period?.toString() ?: "-",
8693
color = Color.White,
8794
style = MifosTypography.labelMedium,
8895
)
@@ -93,63 +100,65 @@ fun RepaymentScheduleItem(
93100
modifier = Modifier.weight(1f),
94101
) {
95102
Text(
96-
text = dueDate,
97-
style = MifosTypography.labelSmallEmphasized,
103+
text = stringResource(
104+
Res.string.feature_loan_installment_number,
105+
period.period?.let {
106+
"$it${if (it % 100 in 11..13) {
107+
"th"
108+
} else {
109+
when (it % 10) {
110+
1 -> "st"
111+
2 -> "nd"
112+
3 -> "rd"
113+
else -> "th"
114+
}
115+
}}"
116+
} ?: "-",
117+
),
98118
color = MaterialTheme.colorScheme.outline,
119+
style = MifosTypography.labelMediumEmphasized,
99120
)
100-
Text(
101-
text = amount,
102-
style = MifosTypography.titleSmallEmphasized,
103-
color = AppColors.customBlack,
104121

105-
)
106122
Text(
107-
text = if (isPaid) {
108-
stringResource(Res.string.feature_loan_paid)
109-
} else {
110-
stringResource(
111-
Res.string.feature_loan_due,
112-
)
113-
},
114-
style = MifosTypography.labelSmall.copy(
115-
color = if (isPaid) AppColors.customEnable else MaterialTheme.colorScheme.error,
116-
),
123+
text = dueDate,
124+
style = MifosTypography.labelLargeEmphasized,
125+
color = AppColors.customBlack,
117126
)
118127
}
119-
120-
MifosButton(
121-
modifier = Modifier
122-
.border(
123-
1.dp,
124-
MaterialTheme.colorScheme.secondaryContainer,
125-
DesignToken.shapes.medium,
126-
),
127-
onClick = onPayClick,
128-
enabled = !isPaid,
129-
colors = ButtonDefaults.buttonColors(
130-
containerColor = if (isPaid) {
131-
MaterialTheme.colorScheme.onPrimary
132-
} else {
133-
MaterialTheme
134-
.colorScheme.onPrimary
135-
},
136-
contentColor = if (isPaid) {
137-
MaterialTheme.colorScheme.onSurface
138-
} else {
139-
MaterialTheme.colorScheme.primary
140-
},
141-
),
142-
shape = DesignToken.shapes.medium,
143-
) {
144-
Text(
145-
text = if (isPaid) {
146-
stringResource(Res.string.feature_loan_paid)
147-
} else {
148-
stringResource(Res.string.feature_loan_pay)
128+
if (canPay) {
129+
MifosButton(
130+
modifier = Modifier
131+
.wrapContentWidth()
132+
.height(DesignToken.sizes.buttonHeight),
133+
onClick = onPayClick,
134+
text = {
135+
Text(
136+
text = stringResource(Res.string.feature_loan_repayment_pay, amount),
137+
style = MifosTypography.titleMedium,
138+
)
149139
},
150-
style = MifosTypography.labelLarge,
151-
color = MaterialTheme.colorScheme.onSurface,
152140
)
141+
} else {
142+
Column(
143+
horizontalAlignment = Alignment.End,
144+
modifier = Modifier.wrapContentWidth(),
145+
) {
146+
Text(
147+
text = if (isPaid) {
148+
stringResource(Res.string.feature_loan_paid)
149+
} else {
150+
stringResource(Res.string.feature_loan_due)
151+
},
152+
style = MifosTypography.labelSmall.copy(
153+
color = if (isPaid) AppColors.customEnable else MaterialTheme.colorScheme.error,
154+
),
155+
)
156+
Text(
157+
text = amount,
158+
style = MifosTypography.titleSmallEmphasized,
159+
color = if (isPaid) AppColors.customEnable else MaterialTheme.colorScheme.error,
160+
)
161+
}
153162
}
154163
}
155164
}

feature/loan-account/src/commonMain/kotlin/org/mifos/mobile/feature/loanaccount/loanAccountRepaymentSchedule/RepaymentScheduleRoute.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.navigation.NavController
1313
import androidx.navigation.NavGraphBuilder
1414
import androidx.navigation.NavOptions
1515
import kotlinx.serialization.Serializable
16+
import org.mifos.mobile.core.model.entity.AccountDetails
1617
import org.mifos.mobile.core.ui.composableWithSlideTransitions
1718

1819
@Serializable
@@ -28,11 +29,13 @@ fun NavController.navigateToLoanRepaymentScreen(
2829
}
2930

3031
fun NavGraphBuilder.loanAccountRepaymentDestination(
32+
navigateToMakePaymentScreen: (AccountDetails) -> Unit,
3133
navigateBack: () -> Unit,
3234
) {
3335
composableWithSlideTransitions<RepaymentScheduleRoute> {
3436
ChargeDetailScreen(
3537
navigateBack = navigateBack,
38+
navigateToMakePaymentScreen = navigateToMakePaymentScreen,
3639
)
3740
}
3841
}

feature/loan-account/src/commonMain/kotlin/org/mifos/mobile/feature/loanaccount/loanAccountRepaymentSchedule/RepaymentScheduleScreen.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@ import mifos_mobile.feature.loan_account.generated.resources.repayment_schedule
3131
import org.jetbrains.compose.resources.stringResource
3232
import org.jetbrains.compose.ui.tooling.preview.Preview
3333
import org.koin.compose.viewmodel.koinViewModel
34+
import org.mifos.mobile.core.common.Constants
3435
import org.mifos.mobile.core.designsystem.component.MifosElevatedScaffold
3536
import org.mifos.mobile.core.designsystem.theme.DesignToken
3637
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
38+
import org.mifos.mobile.core.model.entity.AccountDetails
39+
import org.mifos.mobile.core.model.entity.TransferSuccessDestination
3740
import org.mifos.mobile.core.model.entity.accounts.loan.Periods
41+
import org.mifos.mobile.core.model.enums.TransferType
3842
import org.mifos.mobile.core.ui.component.MifosDetailsCard
3943
import org.mifos.mobile.core.ui.component.MifosErrorComponent
4044
import org.mifos.mobile.core.ui.component.MifosPoweredCard
@@ -45,13 +49,26 @@ import org.mifos.mobile.feature.loanaccount.component.RepaymentScheduleItem
4549
@Composable
4650
internal fun ChargeDetailScreen(
4751
navigateBack: () -> Unit,
52+
navigateToMakePaymentScreen: (AccountDetails) -> Unit,
4853
viewModel: RepaymentScheduleViewModel = koinViewModel(),
4954
) {
5055
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
5156

5257
EventsEffect(viewModel.eventFlow) { event ->
5358
when (event) {
5459
RepaymentScheduleEvent.NavigateBack -> navigateBack.invoke()
60+
61+
is RepaymentScheduleEvent.PayInstallment -> {
62+
navigateToMakePaymentScreen(
63+
AccountDetails(
64+
accountId = event.accountId,
65+
outstandingBalance = event.outStandingBalance,
66+
transferType = event.transferTyp,
67+
transferTarget = event.transferTarget,
68+
transferSuccessDestination = event.transferSuccessDestination,
69+
),
70+
)
71+
}
5572
}
5673
}
5774

@@ -108,10 +125,20 @@ internal fun RepaymentScreenContent(
108125
Spacer(Modifier.height(DesignToken.padding.large))
109126

110127
RepaymentScheduleList(
111-
periods = state.loanWithAssociations?.repaymentSchedule?.periods.orEmpty(),
128+
periods = state.getPeriods,
112129
currencyCode = state.loanWithAssociations?.currency?.code ?: "",
113130
maxDigits = state.loanWithAssociations?.currency?.decimalPlaces?.toInt(),
114131
onPayClick = { period ->
132+
133+
onAction(
134+
RepaymentScheduleAction.OnPayInstallment(
135+
accountId = state.accountId ?: 0L,
136+
outStandingBalance = period.totalDueForPeriod ?: 0.0,
137+
transferTyp = Constants.TRANSFER_PAY_TO,
138+
transferTarget = TransferType.SELF,
139+
transferSuccessDestination = TransferSuccessDestination.LOAN_ACCOUNT,
140+
),
141+
)
115142
},
116143
)
117144
}

feature/loan-account/src/commonMain/kotlin/org/mifos/mobile/feature/loanaccount/loanAccountRepaymentSchedule/RepaymentScheduleViewModel.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import org.mifos.mobile.core.common.DataState
2929
import org.mifos.mobile.core.common.DateHelper
3030
import org.mifos.mobile.core.data.repository.LoanRepository
3131
import org.mifos.mobile.core.data.util.NetworkMonitor
32+
import org.mifos.mobile.core.model.entity.TransferSuccessDestination
3233
import org.mifos.mobile.core.model.entity.accounts.loan.LoanWithAssociations
34+
import org.mifos.mobile.core.model.entity.accounts.loan.Periods
35+
import org.mifos.mobile.core.model.enums.TransferType
3336
import org.mifos.mobile.core.ui.utils.BaseViewModel
3437

3538
internal class RepaymentScheduleViewModel(
@@ -64,6 +67,18 @@ internal class RepaymentScheduleViewModel(
6467

6568
is RepaymentScheduleAction.Internal.ReceivedRepaymentSchedule ->
6669
handleRepaymentScheduleResult(action.dataState)
70+
71+
is RepaymentScheduleAction.OnPayInstallment -> {
72+
sendEvent(
73+
RepaymentScheduleEvent.PayInstallment(
74+
action.accountId,
75+
action.outStandingBalance,
76+
action.transferTyp,
77+
action.transferTarget,
78+
action.transferSuccessDestination,
79+
),
80+
)
81+
}
6782
}
6883
}
6984

@@ -106,6 +121,7 @@ internal class RepaymentScheduleViewModel(
106121

107122
is DataState.Success -> {
108123
val result = dataState.data
124+
println("from success ${ result?.repaymentSchedule?.periods}")
109125
val currencyCode = result?.currency?.code
110126
val maxDigits = result?.currency?.decimalPlaces?.toInt()
111127
val basicDetails = mapOf(
@@ -147,6 +163,7 @@ internal class RepaymentScheduleViewModel(
147163
internal data class RepaymentScheduleState(
148164
val accountId: Long? = null,
149165
val loanWithAssociations: LoanWithAssociations? = null,
166+
val periods: List<Periods> = emptyList(),
150167
val basicDetails: Map<StringResource, String?> = emptyMap(),
151168
val dialogState: DialogState?,
152169
val isOnline: Boolean = false,
@@ -156,16 +173,39 @@ internal data class RepaymentScheduleState(
156173

157174
data class Error(val message: String) : DialogState
158175
}
176+
177+
val getPeriods =
178+
loanWithAssociations
179+
?.repaymentSchedule
180+
?.periods
181+
.orEmpty()
182+
.filter { it.period != null }
159183
}
160184

161185
sealed interface RepaymentScheduleEvent {
162186
data object NavigateBack : RepaymentScheduleEvent
187+
188+
data class PayInstallment(
189+
val accountId: Long,
190+
val outStandingBalance: Double?,
191+
val transferTyp: String,
192+
val transferTarget: TransferType,
193+
val transferSuccessDestination: TransferSuccessDestination,
194+
) : RepaymentScheduleEvent
163195
}
164196

165197
sealed interface RepaymentScheduleAction {
166198
data object RetryClicked : RepaymentScheduleAction
167199
data object OnNavigateBack : RepaymentScheduleAction
168200

201+
data class OnPayInstallment(
202+
val accountId: Long,
203+
val outStandingBalance: Double?,
204+
val transferTyp: String,
205+
val transferTarget: TransferType,
206+
val transferSuccessDestination: TransferSuccessDestination,
207+
) : RepaymentScheduleAction
208+
169209
sealed interface Internal : RepaymentScheduleAction {
170210
data class ReceivedRepaymentSchedule(val dataState: DataState<LoanWithAssociations?>) :
171211
Internal

feature/loan-account/src/commonMain/kotlin/org/mifos/mobile/feature/loanaccount/navigation/LoanNavigation.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fun NavGraphBuilder.loanNavGraph(
6161

6262
loanAccountRepaymentDestination(
6363
navigateBack = navController::popBackStack,
64+
navigateToMakePaymentScreen = navigateToMakePaymentScreen,
6465
)
6566
}
6667
}

feature/transfer-process/src/commonMain/kotlin/org/mifos/mobile/feature/transfer/process/makeTransfer/MakeTransferNavigation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import org.mifos.mobile.core.ui.composableWithSlideTransitions
2424
@Serializable
2525
data class MakeTransferRoute(
2626
val accountId: Long = -1L,
27+
val accountNo: String? = null,
28+
val amount: Double? = null,
2729
val outstandingBalance: Double? = null,
2830
val transferType: String? = null,
2931
val transferTarget: String? = null,
@@ -34,6 +36,7 @@ fun NavController.navigateToMakeTransferScreen(transferPayload: AccountDetails,
3436
navigate(
3537
MakeTransferRoute(
3638
accountId = transferPayload.accountId,
39+
accountNo = transferPayload.accountNo,
3740
outstandingBalance = transferPayload.outstandingBalance,
3841
transferType = transferPayload.transferType,
3942
transferTarget = transferPayload.transferTarget.name,

feature/transfer-process/src/commonMain/kotlin/org/mifos/mobile/feature/transfer/process/makeTransfer/MakeTransferScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ internal fun MakeTransferScreenContent(
156156
optionsList = state.toAccountOptions.map
157157
{ Pair(it.accountNo ?: "", it.clientName ?: "") },
158158
selectedOption = state.toAccount?.accountNo ?: "",
159-
isEnabled = true,
159+
isEnabled = state.outstandingBalance == null,
160160
labelResId = Res.string.pay_to,
161161
onClick = { index, _ ->
162162
onAction(
@@ -182,6 +182,7 @@ internal fun MakeTransferScreenContent(
182182
shape = DesignToken.shapes.medium,
183183
textStyle = MifosTypography.bodyLarge,
184184
config = MifosTextFieldConfig(
185+
enabled = state.outstandingBalance == null,
185186
isError = state.amountError != null,
186187
errorText = state.amountError?.let { stringResource(it) },
187188
trailingIcon = if (state.amountError != null) {

0 commit comments

Comments
 (0)