Skip to content

Commit f6932ab

Browse files
authored
feat: Client upcoming charges UI (openMF#2488)
1 parent 8eef36b commit f6932ab

11 files changed

Lines changed: 613 additions & 58 deletions

File tree

core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosActionsListingCardComponent.kt

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import androidclient.core.ui.generated.resources.core_ui_identify_documents
2525
import androidclient.core.ui.generated.resources.core_ui_last_active
2626
import androidclient.core.ui.generated.resources.core_ui_loan_balance
2727
import androidclient.core.ui.generated.resources.core_ui_loan_product
28-
import androidclient.core.ui.generated.resources.core_ui_name
2928
import androidclient.core.ui.generated.resources.core_ui_note_createdBy
3029
import androidclient.core.ui.generated.resources.core_ui_note_date
3130
import androidclient.core.ui.generated.resources.core_ui_note_note
@@ -437,11 +436,17 @@ fun MifosActionsShareListingComponent(
437436
) {
438437
MifosListingRowItem(
439438
key = stringResource(Res.string.client_share_accounts_pending_for_approval_shares),
440-
value = (pendingForApprovalShares ?: stringResource(Res.string.listing_component_identifier_not_available)).toString(),
439+
value = (
440+
pendingForApprovalShares
441+
?: stringResource(Res.string.listing_component_identifier_not_available)
442+
).toString(),
441443
)
442444
MifosListingRowItem(
443445
key = stringResource(Res.string.client_share_accounts_approved_shares),
444-
value = (approvedShares ?: stringResource(Res.string.listing_component_identifier_not_available)).toString(),
446+
value = (
447+
approvedShares
448+
?: stringResource(Res.string.listing_component_identifier_not_available)
449+
).toString(),
445450
)
446451
}
447452
}
@@ -508,8 +513,7 @@ fun MifosActionsSavingsListingComponent(
508513
isExpanded = isExpanded,
509514
) {
510515
Column(
511-
modifier = Modifier.padding(DesignToken.padding.large)
512-
.clickable { isExpanded = !isExpanded },
516+
modifier = Modifier.padding(DesignToken.padding.large),
513517
) {
514518
MifosListingRowItemHeader(
515519
text = accountNo,
@@ -590,21 +594,23 @@ fun MifosActionsClientFeeListingComponent(
590594
due: String,
591595
paid: String,
592596
waived: String,
597+
isActive: Boolean,
598+
onClick: () -> Unit,
593599
outstanding: String,
594600
menuList: List<Actions>,
595601
onActionClicked: (Actions) -> Unit,
596602
) {
597-
MifosActionsListingComponentOutline {
603+
MifosActionsListingComponentOutline(isExpanded = isActive) {
598604
Column {
599605
Column(
600-
modifier = Modifier.padding(DesignToken.padding.large),
606+
modifier = Modifier.padding(DesignToken.padding.large)
607+
.clickable { onClick() },
601608
) {
602-
MifosListingRowItem(
603-
key = stringResource(Res.string.core_ui_name),
604-
value = name,
609+
MifosListingRowItemHeader(
610+
text = name,
605611
keyStyle = MifosTypography.titleSmallEmphasized,
606-
valueStyle = MifosTypography.titleSmall,
607612
)
613+
608614
Spacer(Modifier.height(DesignToken.padding.large))
609615
MifosListingRowItem(
610616
key = stringResource(Res.string.core_ui_due_as_of),
@@ -636,41 +642,43 @@ fun MifosActionsClientFeeListingComponent(
636642
value = outstanding,
637643
)
638644
}
639-
Surface(
640-
modifier = Modifier.fillMaxWidth(),
641-
shape = RoundedCornerShape(
642-
bottomStart = DesignToken.padding.medium,
643-
bottomEnd = DesignToken.padding.medium,
644-
),
645-
color = MaterialTheme.colorScheme.surfaceContainer,
646-
) {
647-
Column(
648-
modifier = Modifier.padding(
649-
vertical = DesignToken.padding.small,
645+
if (isActive) {
646+
Surface(
647+
modifier = Modifier.fillMaxWidth(),
648+
shape = RoundedCornerShape(
649+
bottomStart = DesignToken.padding.medium,
650+
bottomEnd = DesignToken.padding.medium,
650651
),
652+
color = MaterialTheme.colorScheme.surfaceContainer,
651653
) {
652-
menuList.map { menuItem ->
653-
Row(
654-
modifier = Modifier.fillMaxWidth()
655-
.height(DesignToken.sizes.avatarMedium)
656-
.clickable {
657-
onActionClicked(menuItem)
658-
},
659-
verticalAlignment = Alignment.CenterVertically,
660-
horizontalArrangement = Arrangement.Start,
661-
) {
662-
Icon(
663-
modifier = Modifier.padding(horizontal = DesignToken.padding.large),
664-
imageVector = menuItem.icon,
665-
contentDescription = "",
666-
)
654+
Column(
655+
modifier = Modifier.padding(
656+
vertical = DesignToken.padding.small,
657+
),
658+
) {
659+
menuList.map { menuItem ->
660+
Row(
661+
modifier = Modifier.fillMaxWidth()
662+
.height(DesignToken.sizes.avatarMedium)
663+
.clickable {
664+
onActionClicked(menuItem)
665+
},
666+
verticalAlignment = Alignment.CenterVertically,
667+
horizontalArrangement = Arrangement.Start,
668+
) {
669+
Icon(
670+
modifier = Modifier.padding(horizontal = DesignToken.padding.large),
671+
imageVector = menuItem.icon,
672+
contentDescription = "",
673+
)
667674

668-
Text(
669-
modifier = Modifier.fillMaxWidth(),
670-
text = menuItem::class.simpleName ?: "",
671-
color = MaterialTheme.colorScheme.onSurface,
672-
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
673-
)
675+
Text(
676+
modifier = Modifier.fillMaxWidth(),
677+
text = menuItem::class.simpleName ?: "",
678+
color = MaterialTheme.colorScheme.onSurface,
679+
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
680+
)
681+
}
674682
}
675683
}
676684
}
@@ -684,6 +692,9 @@ sealed class Actions(open val icon: ImageVector) {
684692
data class ApproveAccount(override val icon: ImageVector = MifosIcons.ApproveAccount) :
685693
Actions(icon)
686694

695+
data class PayOutstandingAmount(override val icon: ImageVector = MifosIcons.MakeRepayment) :
696+
Actions(icon)
697+
687698
data class MakeRepayment(override val icon: ImageVector = MifosIcons.MakeRepayment) :
688699
Actions(icon)
689700

@@ -863,6 +874,8 @@ private fun PreviewMifosActionsClientFeeListingComponent() {
863874
Actions.ViewAccount(),
864875
Actions.ApproveAccount(),
865876
),
877+
isActive = true,
878+
onClick = {},
866879
onActionClicked = { action ->
867880
when (action) {
868881
is Actions.ViewAccount -> println(Actions.ViewDocument::class.simpleName)

core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosAlertDialog.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ package com.mifos.core.ui.components
1111

1212
import androidx.compose.material3.AlertDialog
1313
import androidx.compose.material3.Icon
14+
import androidx.compose.material3.MaterialTheme
1415
import androidx.compose.material3.Text
16+
import androidx.compose.material3.TextButton
1517
import androidx.compose.runtime.Composable
1618
import androidx.compose.ui.Modifier
1719
import androidx.compose.ui.graphics.vector.ImageVector
18-
import com.mifos.core.designsystem.component.MifosTextButton
1920
import com.mifos.core.designsystem.theme.MifosTheme
2021
import com.mifos.core.ui.util.DevicePreview
2122

2223
@Composable
2324
fun MifosAlertDialog(
24-
dialogTitle: String,
25+
dialogTitle: String? = null,
2526
dialogText: String,
2627
dismissText: String? = "Cancel",
2728
confirmationText: String = "Ok",
@@ -36,22 +37,20 @@ fun MifosAlertDialog(
3637
Icon(imageVector = icon, contentDescription = null)
3738
}
3839
},
39-
title = { Text(text = dialogTitle) },
40+
title = { if (dialogTitle != null) Text(text = dialogTitle) },
4041
text = { Text(text = dialogText) },
4142
modifier = modifier,
4243
onDismissRequest = onDismissRequest,
4344
confirmButton = {
44-
MifosTextButton(
45-
text = { Text(text = confirmationText) },
46-
onClick = onConfirmation,
47-
)
45+
TextButton(onClick = onConfirmation) {
46+
Text(text = confirmationText, color = MaterialTheme.colorScheme.primary)
47+
}
4848
},
4949
dismissButton = {
5050
if (dismissText != null) {
51-
MifosTextButton(
52-
text = { Text(text = dismissText) },
53-
onClick = onDismissRequest,
54-
)
51+
TextButton(onClick = onDismissRequest) {
52+
Text(text = dismissText, color = MaterialTheme.colorScheme.error)
53+
}
5554
}
5655
},
5756
)

core/ui/src/commonMain/kotlin/com/mifos/core/ui/components/MifosAllUiComponentsPreview.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ private fun PreviewMifosActionsClientFeeListingComponent() {
133133
Actions.ViewAccount(),
134134
Actions.ApproveAccount(),
135135
),
136+
isActive = true,
137+
onClick = {},
136138
onActionClicked = { action ->
137139
when (action) {
138140
is Actions.ViewAccount -> println(Actions.ViewDocument::class.simpleName)
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/android-client/blob/master/LICENSE.md
9+
*/
10+
package com.mifos.feature.client.clientUpcomingCharges
11+
12+
import androidclient.feature.client.generated.resources.Res
13+
import androidclient.feature.client.generated.resources.client_upcoming_charges_failed_message
14+
import androidclient.feature.client.generated.resources.client_upcoming_charges_no_more_charges_available
15+
import androidclient.feature.client.generated.resources.string_not_available
16+
import androidx.compose.foundation.layout.Spacer
17+
import androidx.compose.foundation.layout.fillMaxWidth
18+
import androidx.compose.foundation.layout.padding
19+
import androidx.compose.foundation.lazy.LazyColumn
20+
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.material3.Text
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.LaunchedEffect
24+
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.res.stringResource
26+
import androidx.compose.ui.text.style.TextAlign
27+
import androidx.compose.ui.unit.dp
28+
import androidx.paging.LoadState
29+
import androidx.paging.PagingData
30+
import androidx.paging.compose.collectAsLazyPagingItems
31+
import com.mifos.core.common.utils.DateHelper
32+
import com.mifos.core.designsystem.component.MifosCircularProgress
33+
import com.mifos.core.designsystem.component.MifosPagingAppendProgress
34+
import com.mifos.core.designsystem.component.MifosSweetError
35+
import com.mifos.core.designsystem.theme.DesignToken
36+
import com.mifos.core.ui.components.Actions
37+
import com.mifos.core.ui.components.MifosActionsClientFeeListingComponent
38+
import com.mifos.room.entities.client.ChargesEntity
39+
import kotlinx.coroutines.flow.Flow
40+
import org.jetbrains.compose.resources.stringResource
41+
42+
@Composable
43+
actual fun ChargesListContent(
44+
charges: Flow<PagingData<ChargesEntity>>,
45+
state: ClientUpcomingChargesState,
46+
onAction: (ClientUpcomingChargesAction) -> Unit,
47+
setCount: (Int) -> Unit,
48+
refresh: () -> Unit,
49+
) {
50+
val chargesPagingList = charges.collectAsLazyPagingItems()
51+
52+
when (chargesPagingList.loadState.refresh) {
53+
is LoadState.Error -> MifosSweetError(
54+
message = stringResource(Res.string.client_upcoming_charges_failed_message),
55+
onclick = refresh,
56+
)
57+
58+
LoadState.Loading -> MifosCircularProgress()
59+
60+
is LoadState.NotLoading -> Unit
61+
}
62+
63+
LaunchedEffect(chargesPagingList) {
64+
setCount.invoke(chargesPagingList.itemCount)
65+
}
66+
67+
LazyColumn {
68+
items(
69+
count = chargesPagingList.itemCount,
70+
key = { index -> chargesPagingList[index]?.id ?: index },
71+
) { index ->
72+
chargesPagingList[index]?.let { charge ->
73+
MifosActionsClientFeeListingComponent(
74+
name = charge.name ?: stringResource(Res.string.string_not_available),
75+
dueAsOf = if (charge.dueDate != null) {
76+
DateHelper.getDateAsString(charge.dueDate!!)
77+
} else {
78+
stringResource(Res.string.string_not_available)
79+
},
80+
// todo check if its the right way to get due
81+
due = if (charge.amount != null && charge.amountPaid != null) {
82+
(charge.amount!! - charge.amountPaid!!).toString()
83+
} else {
84+
stringResource(Res.string.string_not_available)
85+
},
86+
paid = charge.amountPaid.toString(),
87+
waived = charge.amountWaived.toString(),
88+
outstanding = charge.amountOutstanding.toString(),
89+
menuList = listOf(
90+
Actions.PayOutstandingAmount(),
91+
),
92+
isActive = index == state.expandedItemIndex,
93+
onClick = { ClientUpcomingChargesAction.CardClicked(index) },
94+
onActionClicked = { actions ->
95+
when (actions) {
96+
is Actions.PayOutstandingAmount -> {
97+
ClientUpcomingChargesAction.PayOutstandingAmount
98+
}
99+
100+
else -> {}
101+
}
102+
},
103+
)
104+
Spacer(modifier = Modifier.padding(bottom = DesignToken.padding.large))
105+
}
106+
}
107+
108+
when (chargesPagingList.loadState.append) {
109+
is LoadState.Error -> {
110+
item {
111+
MifosSweetError(message = org.jetbrains.compose.resources.stringResource(Res.string.client_upcoming_charges_failed_message)) {
112+
refresh()
113+
}
114+
}
115+
}
116+
117+
is LoadState.Loading -> {
118+
item {
119+
MifosPagingAppendProgress()
120+
}
121+
}
122+
123+
is LoadState.NotLoading -> {
124+
if (chargesPagingList.loadState.append.endOfPaginationReached &&
125+
chargesPagingList.itemCount > 0
126+
) {
127+
item {
128+
Text(
129+
modifier = Modifier
130+
.fillMaxWidth()
131+
.padding(6.dp),
132+
text = stringResource(Res.string.client_upcoming_charges_no_more_charges_available),
133+
style = MaterialTheme.typography.bodyMedium,
134+
textAlign = TextAlign.Center,
135+
)
136+
}
137+
}
138+
}
139+
}
140+
}
141+
}

feature/client/src/androidMain/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
-->
1111
<resources>
1212
<string name="feature_client_pinpoint_google_maps_key" templateMergeStrategy="preserve" translatable="false">AIzaSyBbeT2BaMWLj-lReCgYoNmXs_TIyRLr9qQ</string>
13+
1314
</resources>

feature/client/src/commonMain/composeResources/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,4 +471,9 @@
471471
<string name="client_share_accounts_an_error_occurred">An error occurred</string>
472472
<string name="client_share_accounts_failed_to_fetch_share_accounts">Failed to fetch share accounts</string>
473473
<string name="client_share_accounts_try_again">Try again</string>
474+
475+
<string name="client_upcoming_charges_failed_message">Failed to get upcoming charges</string>
476+
<string name="client_upcoming_charges_no_more_charges_available">No more charges available!</string>
477+
<string name="client_upcoming_charges_charges_overview">Charges Overview</string>
478+
474479
</resources>

0 commit comments

Comments
 (0)