Refactor- [:feature:savings] Apply & Fix Detekt, Ktlint Rules (#2688)

Jira Tasks - [MM-76](https://mifosforge.jira.com/browse/MM-76)
This commit is contained in:
Sk Niyaj Ali 2024-09-02 20:55:06 +05:30 committed by GitHub
parent aed62abb79
commit 25389dd8a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 996 additions and 571 deletions

View File

@ -20,6 +20,7 @@ import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -144,6 +145,21 @@ fun MifosOutlinedTextButton(
)
}
@Composable
fun MifosOutlinedButton(
textResId: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
OutlinedButton(
onClick = onClick,
modifier = modifier,
content = {
Text(text = stringResource(id = textResId))
},
)
}
@Composable
fun MifosIconTextButton(
text: String,

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.feature)
alias(libs.plugins.mifos.android.library.compose)

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.navigation
import androidx.navigation.NavController
@ -13,20 +22,28 @@ import org.mifos.mobile.core.common.Constants.SAVINGS_ID
import org.mifos.mobile.core.common.Constants.TRANSFER_PAY_FROM
import org.mifos.mobile.core.common.Constants.TRANSFER_PAY_TO
import org.mifos.mobile.core.common.Constants.TRANSFER_TYPE
import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociations
import org.mifos.mobile.core.model.entity.payload.ReviewTransferPayload
import org.mifos.mobile.core.model.enums.ChargeType
import org.mifos.mobile.core.model.enums.LoanState
import org.mifos.mobile.core.model.enums.SavingsAccountState
import org.mifos.mobile.core.model.enums.TransferType
import org.mifos.mobile.feature.savings.savings_account.SavingsAccountDetailScreen
import org.mifos.mobile.feature.savings.savings_account_application.SavingsAccountApplicationScreen
import org.mifos.mobile.feature.savings.savings_account_transaction.SavingsAccountTransactionScreen
import org.mifos.mobile.feature.savings.savings_account_withdraw.SavingsAccountWithdrawScreen
import org.mifos.mobile.feature.savings.savings_make_transfer.SavingsMakeTransferScreen
import org.mifos.mobile.feature.savings.savingsAccount.SavingsAccountDetailScreen
import org.mifos.mobile.feature.savings.savingsAccountApplication.SavingsAccountApplicationScreen
import org.mifos.mobile.feature.savings.savingsAccountTransaction.SavingsAccountTransactionScreen
import org.mifos.mobile.feature.savings.savingsAccountWithdraw.SavingsAccountWithdrawScreen
import org.mifos.mobile.feature.savings.savingsMakeTransfer.SavingsMakeTransferScreen
fun NavController.navigateToSavingsMakeTransfer(accountId: Long, outstandingBalance: Double? = null, transferType: String) {
navigate(SavingsNavigation.SavingsMakeTransfer.passArguments(accountId, (outstandingBalance ?: 0.0).toString(), transferType))
fun NavController.navigateToSavingsMakeTransfer(
accountId: Long,
outstandingBalance: Double? = null,
transferType: String,
) {
navigate(
SavingsNavigation.SavingsMakeTransfer.passArguments(
accountId,
(outstandingBalance ?: 0.0).toString(),
transferType,
),
)
}
fun NavController.navigateToSavingsDetailScreen(savingsId: Long) {
@ -37,8 +54,8 @@ fun NavController.navigateToSavingsApplicationScreen() {
navigate(
SavingsNavigation.SavingsApplication.passArguments(
savingsId = -1L,
savingsAccountState = SavingsAccountState.CREATE
)
savingsAccountState = SavingsAccountState.CREATE,
),
)
}
@ -47,7 +64,7 @@ fun NavGraphBuilder.savingsNavGraph(
viewQrCode: (String) -> Unit,
viewCharges: (ChargeType) -> Unit,
reviewTransfer: (ReviewTransferPayload, TransferType) -> Unit,
callHelpline: () -> Unit
callHelpline: () -> Unit,
) {
navigation(
startDestination = SavingsNavigation.SavingsDetail.route,
@ -55,14 +72,39 @@ fun NavGraphBuilder.savingsNavGraph(
) {
savingsDetailRoute(
callUs = callHelpline,
deposit = { navController.navigateToSavingsMakeTransfer(accountId = it, transferType = TRANSFER_PAY_TO) },
makeTransfer = { navController.navigateToSavingsMakeTransfer(accountId = it, transferType = TRANSFER_PAY_FROM) },
deposit = {
navController.navigateToSavingsMakeTransfer(
accountId = it,
transferType = TRANSFER_PAY_TO,
)
},
makeTransfer = {
navController.navigateToSavingsMakeTransfer(
accountId = it,
transferType = TRANSFER_PAY_FROM,
)
},
navigateBack = navController::popBackStack,
updateSavingsAccount = { navController.navigate(SavingsNavigation.SavingsApplication.passArguments(savingsId = it, savingsAccountState = SavingsAccountState.UPDATE)) },
updateSavingsAccount = {
navController.navigate(
SavingsNavigation.SavingsApplication.passArguments(
savingsId = it,
savingsAccountState = SavingsAccountState.UPDATE,
),
)
},
viewCharges = { viewCharges(ChargeType.SAVINGS) },
viewQrCode = viewQrCode,
viewTransaction = { navController.navigate(SavingsNavigation.SavingsTransaction.passArguments(it)) },
withdrawSavingsAccount = { navController.navigate(SavingsNavigation.SavingsWithdraw.passArguments(it)) }
viewTransaction = {
navController.navigate(
SavingsNavigation.SavingsTransaction.passArguments(it),
)
},
withdrawSavingsAccount = {
navController.navigate(
SavingsNavigation.SavingsWithdraw.passArguments(it),
)
},
)
savingsApplication(
@ -79,7 +121,7 @@ fun NavGraphBuilder.savingsNavGraph(
savingsMakeTransfer(
navigateBack = navController::popBackStack,
reviewTransfer = reviewTransfer
reviewTransfer = reviewTransfer,
)
}
}
@ -93,13 +135,13 @@ fun NavGraphBuilder.savingsDetailRoute(
viewCharges: () -> Unit,
viewQrCode: (String) -> Unit,
callUs: () -> Unit,
deposit: (Long) -> Unit
deposit: (Long) -> Unit,
) {
composable(
route = SavingsNavigation.SavingsDetail.route,
arguments = listOf(
navArgument(name = SAVINGS_ID) { type = NavType.LongType },
)
),
) {
SavingsAccountDetailScreen(
navigateBack = navigateBack,
@ -116,14 +158,16 @@ fun NavGraphBuilder.savingsDetailRoute(
}
fun NavGraphBuilder.savingsApplication(
navigateBack: () -> Unit
navigateBack: () -> Unit,
) {
composable(
route = SavingsNavigation.SavingsApplication.route,
arguments = listOf(
navArgument(name = SAVINGS_ID) { type = NavType.LongType },
navArgument(Constants.SAVINGS_ACCOUNT_STATE) { type = NavType.EnumType(SavingsAccountState::class.java) },
)
navArgument(Constants.SAVINGS_ACCOUNT_STATE) {
type = NavType.EnumType(SavingsAccountState::class.java)
},
),
) {
SavingsAccountApplicationScreen(
navigateBack = navigateBack,
@ -136,7 +180,7 @@ fun NavGraphBuilder.savingsTransaction(
) {
composable(
route = SavingsNavigation.SavingsTransaction.route,
arguments = listOf(navArgument(name = SAVINGS_ID) { type = NavType.LongType })
arguments = listOf(navArgument(name = SAVINGS_ID) { type = NavType.LongType }),
) {
SavingsAccountTransactionScreen(
navigateBack = navigateBack,
@ -149,7 +193,7 @@ fun NavGraphBuilder.savingsWithdraw(
) {
composable(
route = SavingsNavigation.SavingsWithdraw.route,
arguments = listOf(navArgument(SAVINGS_ID) { type = NavType.LongType })
arguments = listOf(navArgument(SAVINGS_ID) { type = NavType.LongType }),
) {
SavingsAccountWithdrawScreen(
navigateBack = { navigateBack() },
@ -159,7 +203,7 @@ fun NavGraphBuilder.savingsWithdraw(
fun NavGraphBuilder.savingsMakeTransfer(
navigateBack: () -> Unit,
reviewTransfer: (ReviewTransferPayload, TransferType) -> Unit
reviewTransfer: (ReviewTransferPayload, TransferType) -> Unit,
) {
composable(
route = SavingsNavigation.SavingsMakeTransfer.route,
@ -171,12 +215,12 @@ fun NavGraphBuilder.savingsMakeTransfer(
defaultValue = null
},
navArgument(name = TRANSFER_TYPE) { type = NavType.StringType },
)
),
) {
SavingsMakeTransferScreen(
navigateBack = navigateBack,
onCancelledClicked = navigateBack,
reviewTransfer = reviewTransfer
reviewTransfer = reviewTransfer,
)
}
}

View File

@ -1,12 +1,19 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.navigation
import org.mifos.mobile.core.common.Constants
import org.mifos.mobile.core.common.Constants.ACCOUNT_ID
import org.mifos.mobile.core.common.Constants.OUTSTANDING_BALANCE
import org.mifos.mobile.core.common.Constants.SAVINGS_ACCOUNT_STATE
import org.mifos.mobile.core.common.Constants.SAVINGS_ID
import org.mifos.mobile.core.common.Constants.TRANSFER_TYPE
import org.mifos.mobile.core.model.enums.LoanState
import org.mifos.mobile.core.model.enums.SavingsAccountState
const val SAVINGS_NAVIGATION_ROUTE_BASE = "savings_route"
@ -18,22 +25,26 @@ const val SAVINGS_MAKE_TRANSFER_SCREEN_ROUTE = "savings_make_transfer_screen_rou
sealed class SavingsNavigation(val route: String) {
data object SavingsBase : SavingsNavigation(
route = SAVINGS_NAVIGATION_ROUTE_BASE
route = SAVINGS_NAVIGATION_ROUTE_BASE,
)
data object SavingsDetail : SavingsNavigation(
route = "$SAVINGS_DETAIL_SCREEN_ROUTE/{$SAVINGS_ID}"
route = "$SAVINGS_DETAIL_SCREEN_ROUTE/{$SAVINGS_ID}",
) {
fun passArguments(savingsId: Long) = "$SAVINGS_DETAIL_SCREEN_ROUTE/$savingsId"
}
data object SavingsApplication : SavingsNavigation(
route = "$SAVINGS_APPLICATION_SCREEN_ROUTE/{${SAVINGS_ID}}/{${SAVINGS_ACCOUNT_STATE}}") {
fun passArguments(savingsId: Long, savingsAccountState: SavingsAccountState) = "$SAVINGS_APPLICATION_SCREEN_ROUTE/${savingsId}/${savingsAccountState}"
route = "$SAVINGS_APPLICATION_SCREEN_ROUTE/{${SAVINGS_ID}}/{${SAVINGS_ACCOUNT_STATE}}",
) {
fun passArguments(
savingsId: Long,
savingsAccountState: SavingsAccountState,
) = "$SAVINGS_APPLICATION_SCREEN_ROUTE/$savingsId/$savingsAccountState"
}
data object SavingsTransaction : SavingsNavigation(
route = "$SAVINGS_TRANSACTION_SCREEN_ROUTE/{${SAVINGS_ID}}"
route = "$SAVINGS_TRANSACTION_SCREEN_ROUTE/{${SAVINGS_ID}}",
) {
fun passArguments(savingsId: Long): String {
return "$SAVINGS_TRANSACTION_SCREEN_ROUTE/$savingsId"
@ -41,7 +52,7 @@ sealed class SavingsNavigation(val route: String) {
}
data object SavingsWithdraw : SavingsNavigation(
route = "$SAVINGS_WITHDRAW_SCREEN_ROUTE/{${SAVINGS_ID}}"
route = "$SAVINGS_WITHDRAW_SCREEN_ROUTE/{${SAVINGS_ID}}",
) {
fun passArguments(savingsId: Long): String {
return "$SAVINGS_WITHDRAW_SCREEN_ROUTE/$savingsId"
@ -49,9 +60,13 @@ sealed class SavingsNavigation(val route: String) {
}
data object SavingsMakeTransfer : SavingsNavigation(
route = "$SAVINGS_MAKE_TRANSFER_SCREEN_ROUTE/{$ACCOUNT_ID}/{$OUTSTANDING_BALANCE}/{$TRANSFER_TYPE}"
route = "$SAVINGS_MAKE_TRANSFER_SCREEN_ROUTE/{$ACCOUNT_ID}/{$OUTSTANDING_BALANCE}/{$TRANSFER_TYPE}",
) {
fun passArguments(accountId: Long, outstandingBalance: String? = null, transferType: String): String {
fun passArguments(
accountId: Long,
outstandingBalance: String? = null,
transferType: String,
): String {
return "$SAVINGS_MAKE_TRANSFER_SCREEN_ROUTE/$accountId/$outstandingBalance/$transferType"
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccount
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.SavedStateHandle
@ -26,18 +35,20 @@ import org.mifos.mobile.feature.savings.R
import javax.inject.Inject
@HiltViewModel
class SavingAccountsDetailViewModel @Inject constructor(
internal class SavingAccountsDetailViewModel @Inject constructor(
private val savingsAccountRepositoryImp: SavingsAccountRepository,
savedStateHandle: SavedStateHandle,
private var preferencesHelper: PreferencesHelper
private var preferencesHelper: PreferencesHelper,
) : ViewModel() {
val savingsId = savedStateHandle.getStateFlow<Long?>(key = Constants.SAVINGS_ID, initialValue = null)
val savingsId =
savedStateHandle.getStateFlow<Long?>(key = Constants.SAVINGS_ID, initialValue = null)
val savingAccountsDetailUiState = savingsId
.flatMapLatest {
savingsAccountRepositoryImp.getSavingsWithAssociations(
savingsId.value, Constants.TRANSACTIONS,
savingsId.value,
Constants.TRANSACTIONS,
)
}
.asResult()
@ -50,7 +61,7 @@ class SavingAccountsDetailViewModel @Inject constructor(
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = SavingsAccountDetailUiState.Loading
initialValue = SavingsAccountDetailUiState.Loading,
)
fun getQrString(savingsWithAssociations: SavingsWithAssociations?): String {
@ -62,13 +73,13 @@ class SavingAccountsDetailViewModel @Inject constructor(
}
}
sealed class SavingsAccountDetailUiState {
internal sealed class SavingsAccountDetailUiState {
data object Loading : SavingsAccountDetailUiState()
data object Error : SavingsAccountDetailUiState()
data class Success(val savingAccount: SavingsWithAssociations) : SavingsAccountDetailUiState()
}
fun Status.getStatusColorAndText(): Pair<Color, Int> {
internal fun Status.getStatusColorAndText(): Pair<Color, Int> {
return when {
this.active == true -> Pair(DepositGreen, R.string.active)
this.approved == true -> Pair(Blue, R.string.need_approval)
@ -77,5 +88,3 @@ fun Status.getStatusColorAndText(): Pair<Color, Int> {
else -> Pair(Color.Black, R.string.closed)
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccount
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -15,7 +24,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -27,47 +35,49 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import org.mifos.mobile.core.common.utils.CurrencyUtil
import org.mifos.mobile.core.common.utils.DateHelper
import org.mifos.mobile.core.common.utils.SymbolsUtils
import org.mifos.mobile.core.designsystem.components.MifosOutlinedButton
import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociations
import org.mifos.mobile.core.model.entity.accounts.savings.Status
import org.mifos.mobile.core.ui.component.MifosLinkText
import org.mifos.mobile.core.ui.component.MifosTextTitleDescDoubleLine
import org.mifos.mobile.core.ui.component.MonitorListItemWithIcon
import org.mifos.mobile.core.common.utils.CurrencyUtil
import org.mifos.mobile.core.common.utils.DateHelper
import org.mifos.mobile.core.common.utils.SymbolsUtils
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountDetailContent(
internal fun SavingsAccountDetailContent(
savingsAccount: SavingsWithAssociations,
deposit: () -> Unit,
makeTransfer: () -> Unit,
viewTransaction: () -> Unit,
viewCharges: () -> Unit,
viewQrCode: (SavingsWithAssociations) -> Unit,
callUs: () -> Unit
callUs: () -> Unit,
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
val currencySymbol = savingsAccount.currency?.displaySymbol ?: "$"
Column(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.verticalScroll(scrollState)
.padding(16.dp)
.padding(16.dp),
) {
AccountDetailsCard(
makeTransfer = makeTransfer,
deposit = deposit,
currencySymbol = currencySymbol,
savingsAccount = savingsAccount,
currencySymbol = currencySymbol
deposit = deposit,
makeTransfer = makeTransfer,
)
Spacer(modifier = Modifier.height(20.dp))
LastTransactionCard(
savingsWithAssociations = savingsAccount,
currencySymbol = currencySymbol
currencySymbol = currencySymbol,
)
Spacer(modifier = Modifier.height(20.dp))
@ -75,7 +85,7 @@ fun SavingsAccountDetailContent(
SavingsMonitorComponent(
viewTransaction = viewTransaction,
viewCharges = viewCharges,
viewQrCode = { viewQrCode.invoke(savingsAccount) }
viewQrCode = { viewQrCode.invoke(savingsAccount) },
)
Spacer(modifier = Modifier.height(20.dp))
@ -84,29 +94,33 @@ fun SavingsAccountDetailContent(
Text(
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
text = stringResource(id = R.string.need_help)
text = stringResource(id = R.string.need_help),
)
Spacer(modifier = Modifier.width(5.dp))
MifosLinkText(
text = stringResource(id = R.string.help_line_number),
onClick = { callUs.invoke() },
isUnderlined = false
onClick = callUs,
isUnderlined = false,
)
}
}
}
@Composable
fun AccountDetailsCard(
modifier: Modifier = Modifier,
private fun AccountDetailsCard(
currencySymbol: String,
savingsAccount: SavingsWithAssociations,
deposit: () -> Unit,
makeTransfer: () -> Unit,
currencySymbol: String
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
OutlinedCard(modifier = modifier) {
Column(modifier = Modifier.padding(14.dp)) {
Column(
modifier = Modifier
.padding(14.dp),
) {
MifosTextTitleDescDoubleLine(
title = stringResource(id = R.string.account_balance),
description = stringResource(
@ -114,17 +128,19 @@ fun AccountDetailsCard(
currencySymbol,
CurrencyUtil.formatCurrency(
context = context,
amt = savingsAccount.summary?.accountBalance ?: 0.0
)
amt = savingsAccount.summary?.accountBalance ?: 0.0,
),
),
descriptionStyle = MaterialTheme.typography.bodyLarge.copy(
fontWeight = FontWeight.Bold,
),
descriptionStyle = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold)
)
Spacer(modifier = Modifier.height(8.dp))
StatusField(
title = stringResource(id = R.string.account_status),
accountStatus = savingsAccount.status ?: Status()
accountStatus = savingsAccount.status ?: Status(),
)
Spacer(modifier = Modifier.height(8.dp))
@ -142,9 +158,9 @@ fun AccountDetailsCard(
description = stringResource(
id = R.string.double_and_string,
savingsAccount.getNominalAnnualInterestRate(),
SymbolsUtils.PERCENT
SymbolsUtils.PERCENT,
),
descriptionStyle = MaterialTheme.typography.bodyLarge
descriptionStyle = MaterialTheme.typography.bodyLarge,
)
Spacer(modifier = Modifier.height(8.dp))
@ -158,12 +174,12 @@ fun AccountDetailsCard(
CurrencyUtil.formatCurrency(
context = context,
amt = savingsAccount.summary?.totalDeposits ?: 0.0,
)
),
)
} else {
stringResource(id = R.string.not_available)
},
descriptionStyle = MaterialTheme.typography.bodyLarge
descriptionStyle = MaterialTheme.typography.bodyLarge,
)
Spacer(modifier = Modifier.height(8.dp))
@ -173,7 +189,8 @@ fun AccountDetailsCard(
descriptionStyle = MaterialTheme.typography.bodyLarge,
description = if (savingsAccount.summary?.totalDeposits != null) {
stringResource(
id = R.string.string_and_string, currencySymbol,
id = R.string.string_and_string,
currencySymbol,
CurrencyUtil.formatCurrency(
context = context,
amt = savingsAccount.summary?.totalWithdrawals ?: 0.0,
@ -181,35 +198,44 @@ fun AccountDetailsCard(
)
} else {
stringResource(id = R.string.no_withdrawals)
}
},
)
Spacer(modifier = Modifier.height(8.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
OutlinedButton(onClick = {
if(savingsAccount.status?.active == true) {
deposit()
}
}) { Text(text = stringResource(id = R.string.deposit)) }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
MifosOutlinedButton(
textResId = R.string.deposit,
onClick = {
if (savingsAccount.status?.active == true) {
deposit()
}
},
)
Spacer(modifier = Modifier.width(8.dp))
OutlinedButton(onClick = {
if(savingsAccount.status?.active == true) {
makeTransfer()
}
}) { Text(text = stringResource(id = R.string.make_transfer)) }
MifosOutlinedButton(
textResId = R.string.make_transfer,
onClick = {
if (savingsAccount.status?.active == true) {
makeTransfer()
}
},
)
}
}
}
}
@Composable
fun LastTransactionCard(
private fun LastTransactionCard(
savingsWithAssociations: SavingsWithAssociations,
currencySymbol: String,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val isTransactionEmpty = savingsWithAssociations.transactions.isEmpty()
@ -219,7 +245,7 @@ fun LastTransactionCard(
text = stringResource(id = R.string.last_trans),
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(8.dp))
@ -235,9 +261,9 @@ fun LastTransactionCard(
stringResource(
id = R.string.string_and_double,
currencySymbol,
savingsWithAssociations.transactions[0].amount ?: 0.0
savingsWithAssociations.transactions[0].amount ?: 0.0,
)
}
},
)
if (!isTransactionEmpty) {
@ -247,7 +273,7 @@ fun LastTransactionCard(
descriptionStyle = MaterialTheme.typography.bodyLarge,
description = DateHelper.getDateAsString(
savingsWithAssociations.lastActiveTransactionDate,
)
),
)
}
@ -257,12 +283,13 @@ fun LastTransactionCard(
title = stringResource(id = R.string.min_required_balance),
descriptionStyle = MaterialTheme.typography.bodyLarge,
description = stringResource(
id = R.string.string_and_string, currencySymbol,
id = R.string.string_and_string,
currencySymbol,
CurrencyUtil.formatCurrency(
context = context,
amt = savingsWithAssociations.minRequiredOpeningBalance ?: 0.0,
),
)
),
)
}
}
@ -271,11 +298,11 @@ fun LastTransactionCard(
}
@Composable
fun SavingsMonitorComponent(
modifier: Modifier = Modifier,
private fun SavingsMonitorComponent(
viewTransaction: () -> Unit,
viewCharges: () -> Unit,
viewQrCode: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier.fillMaxWidth()) {
Text(
@ -291,37 +318,38 @@ fun SavingsMonitorComponent(
titleId = R.string.transactions,
subTitleId = R.string.view_transactions,
iconId = R.drawable.ic_compare_arrows_black_24dp,
onClick = viewTransaction
onClick = viewTransaction,
)
MonitorListItemWithIcon(
titleId = R.string.savings_charges,
subTitleId = R.string.view_charges,
iconId = R.drawable.ic_charges,
onClick = viewCharges
onClick = viewCharges,
)
MonitorListItemWithIcon(
titleId = R.string.qr_code,
subTitleId = R.string.view_qr_code,
iconId = R.drawable.ic_qrcode_scan,
onClick = viewQrCode
onClick = viewQrCode,
)
}
}
@Composable
fun StatusField(
private fun StatusField(
title: String,
accountStatus: Status
accountStatus: Status,
modifier: Modifier = Modifier,
) {
val (color, textResId) = accountStatus.getStatusColorAndText()
Column(modifier = Modifier.fillMaxWidth()) {
Column(modifier = modifier.fillMaxWidth()) {
Text(
text = title,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.alpha(0.7f)
.fillMaxWidth()
.fillMaxWidth(),
)
Row(verticalAlignment = Alignment.CenterVertically) {
@ -329,14 +357,14 @@ fun StatusField(
modifier = Modifier
.clip(CircleShape)
.background(color = color)
.size(15.dp)
.size(15.dp),
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(id = textResId),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccount
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@ -6,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.core.designsystem.components.MifosScaffold
@ -15,11 +23,11 @@ import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociatio
import org.mifos.mobile.core.ui.component.EmptyDataView
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountDetailScreen(
viewModel: SavingAccountsDetailViewModel = hiltViewModel(),
internal fun SavingsAccountDetailScreen(
navigateBack: () -> Unit,
updateSavingsAccount: (Long) -> Unit,
withdrawSavingsAccount: (Long) -> Unit,
@ -28,27 +36,30 @@ fun SavingsAccountDetailScreen(
viewCharges: () -> Unit,
viewQrCode: (String) -> Unit,
callUs: () -> Unit,
deposit: (Long) -> Unit
deposit: (Long) -> Unit,
modifier: Modifier = Modifier,
viewModel: SavingAccountsDetailViewModel = hiltViewModel(),
) {
val uiState by viewModel.savingAccountsDetailUiState.collectAsStateWithLifecycle()
val savingsId = viewModel.savingsId.collectAsStateWithLifecycle().value ?: -1L
SavingsAccountDetailScreen(
uiState = uiState,
navigateBack = navigateBack,
updateSavingsAccount = { updateSavingsAccount(viewModel.savingsId.value ?: -1L) },
withdrawSavingsAccount = { withdrawSavingsAccount(viewModel.savingsId.value ?: -1L) },
makeTransfer = { makeTransfer(viewModel.savingsId.value ?: -1L) },
viewTransaction = { viewTransaction(viewModel.savingsId.value ?: -1L) },
modifier = modifier,
updateSavingsAccount = { updateSavingsAccount(savingsId) },
withdrawSavingsAccount = { withdrawSavingsAccount(savingsId) },
makeTransfer = { makeTransfer(savingsId) },
viewTransaction = { viewTransaction(savingsId) },
viewCharges = viewCharges,
viewQrCode = { viewQrCode(viewModel.getQrString(it)) },
callUs = callUs,
deposit = { deposit(viewModel.savingsId.value ?: -1L) },
deposit = { deposit(savingsId) },
)
}
@Composable
fun SavingsAccountDetailScreen(
private fun SavingsAccountDetailScreen(
uiState: SavingsAccountDetailUiState,
navigateBack: () -> Unit,
updateSavingsAccount: () -> Unit,
@ -59,32 +70,30 @@ fun SavingsAccountDetailScreen(
viewQrCode: (SavingsWithAssociations) -> Unit,
callUs: () -> Unit,
deposit: () -> Unit,
modifier: Modifier = Modifier,
) {
MifosScaffold(
topBar = {
SavingsAccountDetailTopBar(
navigateBack = navigateBack,
updateSavingsAccount = updateSavingsAccount,
withdrawSavingsAccount = withdrawSavingsAccount
withdrawSavingsAccount = withdrawSavingsAccount,
)
}
},
modifier = modifier,
) {
Box(modifier = Modifier.padding(it)) {
when (uiState) {
is SavingsAccountDetailUiState.Error -> {
MifosErrorComponent()
}
is SavingsAccountDetailUiState.Error -> MifosErrorComponent()
is SavingsAccountDetailUiState.Loading -> {
MifosProgressIndicatorOverlay()
}
is SavingsAccountDetailUiState.Loading -> MifosProgressIndicatorOverlay()
is SavingsAccountDetailUiState.Success -> {
if (uiState.savingAccount.status?.submittedAndPendingApproval == true) {
EmptyDataView(
modifier = Modifier.fillMaxSize(),
icon = R.drawable.ic_assignment_turned_in_black_24dp,
error = R.string.approval_pending
error = R.string.approval_pending,
)
} else {
SavingsAccountDetailContent(
@ -94,7 +103,7 @@ fun SavingsAccountDetailScreen(
viewTransaction = viewTransaction,
viewQrCode = viewQrCode,
callUs = callUs,
deposit = deposit
deposit = deposit,
)
}
}
@ -103,13 +112,21 @@ fun SavingsAccountDetailScreen(
}
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsAccountDetailScreenPreview() {
private fun SavingsAccountDetailScreenPreview() {
MifosMobileTheme {
SavingsAccountDetailScreen(
uiState = SavingsAccountDetailUiState.Loading,
{}, {}, {}, {}, {}, {}, {}, {}, {}
navigateBack = {},
updateSavingsAccount = {},
withdrawSavingsAccount = {},
makeTransfer = {},
viewTransaction = {},
viewCharges = {},
viewQrCode = {},
callUs = {},
deposit = {},
)
}
}
}

View File

@ -1,8 +1,14 @@
package org.mifos.mobile.feature.savings.savings_account
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccount
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
@ -19,70 +25,76 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import org.mifos.mobile.core.designsystem.icons.MifosIcons
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SavingsAccountDetailTopBar(
internal fun SavingsAccountDetailTopBar(
navigateBack: () -> Unit,
updateSavingsAccount: () -> Unit,
withdrawSavingsAccount: () -> Unit
withdrawSavingsAccount: () -> Unit,
modifier: Modifier = Modifier,
) {
var showMenu by remember { mutableStateOf(false) }
TopAppBar(
modifier = Modifier,
modifier = modifier,
title = { Text(text = stringResource(id = R.string.saving_account_details)) },
navigationIcon = {
IconButton(
onClick = { navigateBack.invoke() }
onClick = { navigateBack.invoke() },
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
imageVector = MifosIcons.ArrowBack,
contentDescription = "Back Arrow",
tint = MaterialTheme.colorScheme.onSurface
tint = MaterialTheme.colorScheme.onSurface,
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
containerColor = MaterialTheme.colorScheme.background,
),
actions = {
IconButton(onClick = { showMenu = !showMenu }) {
Icon(
imageVector = Icons.Filled.MoreVert,
imageVector = MifosIcons.MoreVert,
contentDescription = "Menu",
tint = MaterialTheme.colorScheme.onSurface
tint = MaterialTheme.colorScheme.onSurface,
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
onDismissRequest = { showMenu = false },
) {
DropdownMenuItem(
text = {
Text(text = stringResource(id = R.string.update_savings_account))
},
onClick = { updateSavingsAccount.invoke() }
onClick = { updateSavingsAccount.invoke() },
)
DropdownMenuItem(
text = {
Text(text = stringResource(id = R.string.withdraw_savings_account))
},
onClick = { withdrawSavingsAccount.invoke() }
onClick = { withdrawSavingsAccount.invoke() },
)
}
}
},
)
}
@Preview
@DevicePreviews
@Composable
fun SavingsAccountDetailTopBarPreview() {
private fun SavingsAccountDetailTopBarPreview() {
MifosMobileTheme {
SavingsAccountDetailTopBar({}, {}, {})
SavingsAccountDetailTopBar(
navigateBack = {},
updateSavingsAccount = {},
withdrawSavingsAccount = {},
)
}
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_application
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountApplication
import android.content.Context
import android.widget.Toast
@ -9,10 +18,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.Button
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@ -35,24 +40,26 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.mifos.mobile.core.common.utils.getTodayFormatted
import org.mifos.mobile.core.designsystem.components.MifosButton
import org.mifos.mobile.core.designsystem.icons.MifosIcons
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.core.model.entity.templates.savings.SavingsAccountTemplate
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountApplicationContent(
internal fun SavingsAccountApplicationContent(
submit: (Int, Int, showToast: (Int) -> Unit) -> Unit,
modifier: Modifier = Modifier,
existingProduct: String? = null,
savingsAccountTemplate: SavingsAccountTemplate? = null,
submit: (Int, Int, showToast: (Int) -> Unit) -> Unit
) {
var selectProductId by rememberSaveable { mutableIntStateOf(-1) }
val context = LocalContext.current
Column(modifier = Modifier.padding(16.dp)) {
Column(modifier = modifier.padding(16.dp)) {
OutlinedCard(
colors = CardDefaults.outlinedCardColors(
containerColor = MaterialTheme.colorScheme.background,
@ -61,12 +68,12 @@ fun SavingsAccountApplicationContent(
Column(modifier = Modifier.padding(20.dp)) {
TitleBodyRow(
titleText = stringResource(R.string.client_name),
bodyText = savingsAccountTemplate?.clientName ?: ""
bodyText = savingsAccountTemplate?.clientName ?: "",
)
Spacer(modifier = Modifier.height(16.dp))
TitleBodyRow(
titleText = stringResource(R.string.submission_date),
bodyText = getTodayFormatted()
bodyText = getTodayFormatted(),
)
}
}
@ -75,40 +82,41 @@ fun SavingsAccountApplicationContent(
SelectProductIdDropDown(
existingProduct = existingProduct,
selectProductId = { selectProductId = it },
savingsAccountTemplate = savingsAccountTemplate,
selectProductId = { selectProductId = it }
)
Spacer(modifier = Modifier.height(20.dp))
Button(
MifosButton(
textResId = R.string.submit,
modifier = Modifier.fillMaxWidth(),
onClick = {
submit(selectProductId, savingsAccountTemplate?.clientId ?: -1) {
showToast(context, it)
}
},
content = { Text(text = stringResource(id = R.string.submit)) }
)
}
}
private fun showToast(context: Context, messageResId: Int){
private fun showToast(context: Context, messageResId: Int) {
Toast.makeText(context, context.getString(messageResId), Toast.LENGTH_LONG).show()
}
@Composable
fun SelectProductIdDropDown(
private fun SelectProductIdDropDown(
existingProduct: String?,
selectProductId: (Int) -> Unit,
modifier: Modifier = Modifier,
savingsAccountTemplate: SavingsAccountTemplate? = null,
selectProductId: (Int) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var selectedProduct by remember { mutableStateOf(existingProduct ?: "") }
val productOptions = savingsAccountTemplate?.productOptions.orEmpty()
Column(
modifier = Modifier
modifier = modifier,
) {
OutlinedTextField(
value = selectedProduct,
@ -122,22 +130,25 @@ fun SelectProductIdDropDown(
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledContainerColor = MaterialTheme.colorScheme.background,
disabledLabelColor = MaterialTheme.colorScheme.onSurface
disabledLabelColor = MaterialTheme.colorScheme.onSurface,
),
trailingIcon = {
Icon(
imageVector = if (expanded) Icons.Filled.ArrowDropUp
else Icons.Filled.ArrowDropDown,
imageVector = if (expanded) {
MifosIcons.ArrowDropUp
} else {
MifosIcons.ArrowDropDown
},
contentDescription = "Dropdown",
)
}
},
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp)
.padding(horizontal = 4.dp),
) {
productOptions.forEach { product ->
DropdownMenuItem(
@ -146,7 +157,7 @@ fun SelectProductIdDropDown(
selectedProduct = product.name ?: ""
expanded = false
},
text = { Text(text = product.name ?: "") }
text = { Text(text = product.name ?: "") },
)
}
}
@ -154,30 +165,40 @@ fun SelectProductIdDropDown(
}
@Composable
fun TitleBodyRow(titleText: String, bodyText: String, modifier: Modifier = Modifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
private fun TitleBodyRow(
titleText: String,
bodyText: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = titleText,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.alpha(0.7f)
.weight(2f)
.weight(2f),
)
Text(
text = bodyText,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(3f),
textAlign = TextAlign.Center
textAlign = TextAlign.Center,
)
}
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsAccountApplicationContentPreview() {
private fun SavingsAccountApplicationContentPreview() {
MifosMobileTheme {
SavingsAccountApplicationContent(null, null, {i, j, k -> })
SavingsAccountApplicationContent(
submit = { _, _, _ -> },
existingProduct = null,
savingsAccountTemplate = null,
)
}
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_application
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountApplication
import android.widget.Toast
import androidx.compose.foundation.layout.Box
@ -12,7 +21,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.core.designsystem.components.MifosScaffold
@ -22,29 +30,32 @@ import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociatio
import org.mifos.mobile.core.model.enums.SavingsAccountState
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountApplicationScreen(
viewModel: SavingsAccountApplicationViewModel = hiltViewModel(),
internal fun SavingsAccountApplicationScreen(
navigateBack: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SavingsAccountApplicationViewModel = hiltViewModel(),
) {
val uiState by viewModel.savingsAccountApplicationUiState.collectAsStateWithLifecycle()
SavingsAccountApplicationScreen(
uiState = uiState,
navigateBack = navigateBack,
submit = viewModel::onSubmit
submit = viewModel::onSubmit,
modifier = modifier,
)
}
@Composable
fun SavingsAccountApplicationScreen(
private fun SavingsAccountApplicationScreen(
uiState: SavingsAccountApplicationUiState,
savingsWithAssociations: SavingsWithAssociations? = null,
navigateBack: () -> Unit,
submit: (Int, Int, showToast: (Int) -> Unit) -> Unit,
modifier: Modifier = Modifier,
savingsWithAssociations: SavingsWithAssociations? = null,
) {
var topBarTitleText by rememberSaveable { mutableStateOf("") }
val context = LocalContext.current
@ -53,15 +64,14 @@ fun SavingsAccountApplicationScreen(
topBar = {
MifosTopBar(
navigateBack = navigateBack,
title = { Text(text = topBarTitleText) }
title = { Text(text = topBarTitleText) },
)
},
modifier = modifier,
content = {
Box(modifier = Modifier.padding(it)) {
when (uiState) {
is SavingsAccountApplicationUiState.Error -> {
MifosErrorComponent()
}
is SavingsAccountApplicationUiState.Error -> MifosErrorComponent()
is SavingsAccountApplicationUiState.Loading -> {
MifosProgressIndicatorOverlay()
@ -75,9 +85,9 @@ fun SavingsAccountApplicationScreen(
topBarTitleText = stringResource(id = titleResourceId)
SavingsAccountApplicationContent(
submit = submit,
existingProduct = existingProduct,
savingsAccountTemplate = uiState.template,
submit = submit
)
}
@ -86,21 +96,28 @@ fun SavingsAccountApplicationScreen(
SavingsAccountState.CREATE -> R.string.new_saving_account_created_successfully
else -> R.string.saving_account_updated_successfully
}
Toast.makeText(context, stringResource(id = messageResourceId), Toast.LENGTH_SHORT).show()
Toast.makeText(
context,
stringResource(id = messageResourceId),
Toast.LENGTH_SHORT,
).show()
navigateBack.invoke()
}
}
}
}
},
)
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsAccountApplicationScreenPreview() {
private fun SavingsAccountApplicationScreenPreview() {
MifosMobileTheme {
SavingsAccountApplicationScreen(SavingsAccountApplicationUiState.Success(requestType = SavingsAccountState.UPDATE), null, {}, { i, j, k -> })
SavingsAccountApplicationScreen(
SavingsAccountApplicationUiState.Success(requestType = SavingsAccountState.UPDATE),
navigateBack = {},
submit = { _, _, _ -> },
savingsWithAssociations = null,
)
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_application
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountApplication
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
@ -7,6 +16,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
@ -22,44 +32,46 @@ import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociatio
import org.mifos.mobile.core.model.entity.templates.savings.SavingsAccountTemplate
import org.mifos.mobile.core.model.enums.SavingsAccountState
import org.mifos.mobile.feature.savings.R
import org.mifos.mobile.feature.savings.savingsAccountApplication.SavingsAccountApplicationUiState.Loading
import javax.inject.Inject
@HiltViewModel
class SavingsAccountApplicationViewModel @Inject constructor(
internal class SavingsAccountApplicationViewModel @Inject constructor(
private val savingsAccountRepositoryImp: SavingsAccountRepository,
private val preferencesHelper: PreferencesHelper,
savedStateHandle: SavedStateHandle
savedStateHandle: SavedStateHandle,
) : ViewModel() {
val savingsAccountApplicationUiState: StateFlow<SavingsAccountApplicationUiState> get() = _savingsAccountApplicationUiState
private val _savingsAccountApplicationUiState =
MutableStateFlow<SavingsAccountApplicationUiState>(SavingsAccountApplicationUiState.Loading)
private val clientId get() = preferencesHelper.clientId
val savingsId = savedStateHandle.getStateFlow(key = Constants.SAVINGS_ID, initialValue = -1L)
val savingsAccountState = savedStateHandle.getStateFlow(
private val savingsId =
savedStateHandle.getStateFlow(key = Constants.SAVINGS_ID, initialValue = -1L)
private val savingsAccountState = savedStateHandle.getStateFlow(
key = Constants.SAVINGS_ACCOUNT_STATE,
initialValue = SavingsAccountState.CREATE
initialValue = SavingsAccountState.CREATE,
)
var savingsWithAssociations: StateFlow<SavingsWithAssociations?> = savingsId
private val _savingsAccountApplicationUiState =
MutableStateFlow<SavingsAccountApplicationUiState>(Loading)
val savingsAccountApplicationUiState = _savingsAccountApplicationUiState.asStateFlow()
private val savingsWithAssociations: StateFlow<SavingsWithAssociations?> = savingsId
.flatMapLatest {
savingsAccountRepositoryImp.getSavingsWithAssociations(
savingsId.value, Constants.TRANSACTIONS,
savingsId.value,
Constants.TRANSACTIONS,
)
}
.also { loadSavingsAccountApplicationTemplate() }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = null
initialValue = null,
)
fun loadSavingsAccountApplicationTemplate() {
private fun loadSavingsAccountApplicationTemplate() {
viewModelScope.launch {
_savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading
_savingsAccountApplicationUiState.value = Loading
savingsAccountRepositoryImp.getSavingAccountApplicationTemplate(clientId).catch { e ->
_savingsAccountApplicationUiState.value =
SavingsAccountApplicationUiState.Error(e.message)
@ -67,15 +79,15 @@ class SavingsAccountApplicationViewModel @Inject constructor(
_savingsAccountApplicationUiState.value =
SavingsAccountApplicationUiState.ShowUserInterface(
it,
savingsAccountState.value
savingsAccountState.value,
)
}
}
}
fun submitSavingsAccountApplication(payload: SavingsAccountApplicationPayload?) {
private fun submitSavingsAccountApplication(payload: SavingsAccountApplicationPayload?) {
viewModelScope.launch {
_savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading
_savingsAccountApplicationUiState.value = Loading
savingsAccountRepositoryImp.submitSavingAccountApplication(payload).catch { e ->
_savingsAccountApplicationUiState.value =
SavingsAccountApplicationUiState.Error(e.message)
@ -86,9 +98,9 @@ class SavingsAccountApplicationViewModel @Inject constructor(
}
}
fun updateSavingsAccount(accountId: Long?, payload: SavingsAccountUpdatePayload?) {
private fun updateSavingsAccount(accountId: Long?, payload: SavingsAccountUpdatePayload?) {
viewModelScope.launch {
_savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading
_savingsAccountApplicationUiState.value = Loading
savingsAccountRepositoryImp.updateSavingsAccount(accountId, payload).catch { e ->
_savingsAccountApplicationUiState.value =
SavingsAccountApplicationUiState.Error(e.message)
@ -133,14 +145,13 @@ class SavingsAccountApplicationViewModel @Inject constructor(
}
}
sealed class SavingsAccountApplicationUiState {
internal sealed class SavingsAccountApplicationUiState {
data object Loading : SavingsAccountApplicationUiState()
data class Error(val errorMessage: String?) : SavingsAccountApplicationUiState()
data class Success(val requestType: SavingsAccountState) : SavingsAccountApplicationUiState()
data class ShowUserInterface(
val template: SavingsAccountTemplate,
val requestType: SavingsAccountState
val requestType: SavingsAccountState,
) :
SavingsAccountApplicationUiState()
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_transaction
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountTransaction
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@ -32,12 +41,13 @@ import org.mifos.mobile.core.model.entity.accounts.savings.Transactions
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountTransactionContent(
internal fun SavingsAccountTransactionContent(
transactionList: List<Transactions>,
modifier: Modifier = Modifier,
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween,
) {
LazyColumn {
items(items = transactionList) {
@ -45,7 +55,7 @@ fun SavingsAccountTransactionContent(
HorizontalDivider(
thickness = 1.dp,
color = Color.Gray,
modifier = Modifier.padding(vertical = 4.dp)
modifier = Modifier.padding(vertical = 4.dp),
)
}
}
@ -54,42 +64,44 @@ fun SavingsAccountTransactionContent(
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 10.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.Bottom
verticalAlignment = Alignment.Bottom,
) {
Text(
text = stringResource(id = R.string.need_help),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.width(2.dp))
Text(
text = stringResource(id = R.string.help_line_number),
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.primary,
)
}
}
}
@Composable
fun SavingsAccountTransactionListItem(transaction: Transactions) {
private fun SavingsAccountTransactionListItem(
transaction: Transactions,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
Row(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(vertical = 6.dp)
.padding(vertical = 6.dp),
) {
Image(
painter = painterResource(
id = getTransactionTriangleResId(transaction.transactionType)
id = getTransactionTriangleResId(transaction.transactionType),
),
contentDescription = stringResource(id = R.string.savings_account_transaction),
modifier = Modifier
.size(56.dp)
.padding(4.dp)
.padding(4.dp),
)
Column(
modifier = Modifier.padding(4.dp)
modifier = Modifier.padding(4.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
@ -98,47 +110,47 @@ fun SavingsAccountTransactionListItem(transaction: Transactions) {
Text(
text = DateHelper.getDateAsString(transaction.date),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Text(
text = stringResource(
id = R.string.string_and_string,
transaction.currency?.displaySymbol ?: transaction.currency?.code ?: "",
CurrencyUtil.formatCurrency(context, transaction.amount,)
CurrencyUtil.formatCurrency(context, transaction.amount),
),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = transaction.transactionType?.value ?: "",
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.alpha(0.7f),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Text(
text = stringResource(
id = R.string.string_and_string,
transaction.currency?.displaySymbol ?: transaction.currency?.code ?: "",
CurrencyUtil.formatCurrency(context, transaction.runningBalance)
CurrencyUtil.formatCurrency(context, transaction.runningBalance),
),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.alpha(0.7f),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
}
Row(
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = transaction.paymentDetailData?.paymentType?.name.toString(),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.alpha(0.7f),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
}
}
@ -147,8 +159,8 @@ fun SavingsAccountTransactionListItem(transaction: Transactions) {
@Preview
@Composable
fun SavingsAccountTransactionContentPreview() {
private fun SavingsAccountTransactionContentPreview() {
MifosMobileTheme {
SavingsAccountTransactionContent(transactionList = listOf())
}
}
}

View File

@ -1,5 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_transaction
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountTransaction
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
@ -10,13 +18,13 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -26,26 +34,27 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import org.mifos.mobile.core.common.utils.DateHelper
import org.mifos.mobile.core.common.utils.DateHelper.getDateAsStringFromLong
import org.mifos.mobile.core.designsystem.components.MifosIconTextButton
import org.mifos.mobile.core.designsystem.components.MifosRadioButton
import org.mifos.mobile.core.designsystem.components.MifosTextButton
import org.mifos.mobile.core.designsystem.icons.MifosIcons
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.core.ui.component.MifosCheckBox
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
import java.time.Instant
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SavingsTransactionFilterDialog(
internal fun SavingsTransactionFilterDialog(
onDismiss: () -> Unit,
savingsTransactionFilterDataModel: SavingsTransactionFilterDataModel,
filter: (SavingsTransactionFilterDataModel) -> Unit,
modifier: Modifier = Modifier,
) {
var radioFilter by rememberSaveable { mutableStateOf(savingsTransactionFilterDataModel.radioFilter) }
val checkBoxFilters by rememberSaveable { mutableStateOf(savingsTransactionFilterDataModel.checkBoxFilters) }
var startDate by rememberSaveable { mutableStateOf(savingsTransactionFilterDataModel.startDate) }
@ -58,7 +67,7 @@ fun SavingsTransactionFilterDialog(
LaunchedEffect(key1 = checkBoxFilters) {
checkBoxFilters.forEach { filter ->
when(filter) {
when (filter) {
SavingsTransactionCheckBoxFilter.DEPOSIT -> isDepositChecked = true
SavingsTransactionCheckBoxFilter.DIVIDEND_PAYOUT -> isDividendPayoutChecked = true
SavingsTransactionCheckBoxFilter.WITHDRAWAL -> isWithdrawalChecked = true
@ -67,12 +76,13 @@ fun SavingsTransactionFilterDialog(
}
}
Dialog(
onDismissRequest = { onDismiss.invoke() },
BasicAlertDialog(
onDismissRequest = onDismiss,
modifier = modifier,
) {
Card(shape = RoundedCornerShape(20.dp)) {
Column(
modifier = Modifier.padding(vertical = 20.dp, horizontal = 10.dp)
modifier = Modifier.padding(vertical = 20.dp, horizontal = 10.dp),
) {
Text(text = stringResource(id = R.string.select_you_want))
@ -82,29 +92,41 @@ fun SavingsTransactionFilterDialog(
selectedStartDate = startDate,
selectedEndDate = endDate,
radioFilter = radioFilter,
selectRadioFilter = { radioFilter = it },
setStartDate = { startDate = it },
isDepositChecked = isDepositChecked,
isDividendPayoutChecked = isDividendPayoutChecked,
isWithdrawalChecked = isWithdrawalChecked,
isInterestPostingChecked = isInterestPostingChecked,
isDividendPayoutChecked = isDividendPayoutChecked,
setEndDate = { endDate = it },
selectRadioFilter = { radioFilter = it },
toggleCheckBox = { filter, isEnabled ->
when(filter) {
when (filter) {
SavingsTransactionCheckBoxFilter.DEPOSIT -> isDepositChecked = isEnabled
SavingsTransactionCheckBoxFilter.DIVIDEND_PAYOUT -> isDividendPayoutChecked = isEnabled
SavingsTransactionCheckBoxFilter.WITHDRAWAL -> isWithdrawalChecked = isEnabled
SavingsTransactionCheckBoxFilter.INTEREST_POSTING -> isInterestPostingChecked = isEnabled
SavingsTransactionCheckBoxFilter.DIVIDEND_PAYOUT ->
isDividendPayoutChecked =
isEnabled
SavingsTransactionCheckBoxFilter.WITHDRAWAL ->
isWithdrawalChecked =
isEnabled
SavingsTransactionCheckBoxFilter.INTEREST_POSTING ->
isInterestPostingChecked =
isEnabled
}
if(isEnabled) checkBoxFilters.add(filter)
else checkBoxFilters.remove(filter)
}
if (isEnabled) {
checkBoxFilters.add(filter)
} else {
checkBoxFilters.remove(filter)
}
},
setStartDate = { startDate = it },
setEndDate = { endDate = it },
)
Spacer(modifier = Modifier.height(20.dp))
Row {
TextButton(
MifosTextButton(
text = stringResource(R.string.clear_filters),
onClick = {
radioFilter = null
isDepositChecked = false
@ -112,20 +134,18 @@ fun SavingsTransactionFilterDialog(
isInterestPostingChecked = false
isDividendPayoutChecked = false
checkBoxFilters.clear()
}
) {
Text(text = stringResource(id = R.string.clear_filters))
}
},
)
Spacer(modifier = Modifier.weight(1f))
TextButton(
onClick = { onDismiss() }
) {
Text(text = stringResource(id = R.string.cancel))
}
MifosTextButton(
onClick = onDismiss,
text = stringResource(id = R.string.cancel),
)
TextButton(
MifosTextButton(
text = stringResource(id = R.string.filter),
onClick = {
onDismiss()
filter(
@ -133,13 +153,11 @@ fun SavingsTransactionFilterDialog(
startDate = startDate,
endDate = endDate,
radioFilter = radioFilter,
checkBoxFilters = checkBoxFilters
)
checkBoxFilters = checkBoxFilters,
),
)
}
) {
Text(text = stringResource(id = R.string.filter))
}
},
)
}
}
}
@ -148,23 +166,26 @@ fun SavingsTransactionFilterDialog(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SavingsTransactionFilterDialogContent(
private fun SavingsTransactionFilterDialogContent(
selectedStartDate: Long,
selectedEndDate: Long,
radioFilter: SavingsTransactionRadioFilter?,
selectRadioFilter: (SavingsTransactionRadioFilter) -> Unit,
isDepositChecked: Boolean,
isDividendPayoutChecked: Boolean,
isWithdrawalChecked: Boolean,
isInterestPostingChecked: Boolean,
selectRadioFilter: (SavingsTransactionRadioFilter) -> Unit,
toggleCheckBox: (SavingsTransactionCheckBoxFilter, Boolean) -> Unit,
setStartDate: (Long) -> Unit,
setEndDate: (Long) -> Unit
setEndDate: (Long) -> Unit,
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
var showStartDatePickerDialog by rememberSaveable { mutableStateOf(false) }
var showEndDatePickerDialog by rememberSaveable { mutableStateOf(false) }
val startDatePickerState = rememberDatePickerState(initialSelectedDateMillis = selectedStartDate)
val startDatePickerState =
rememberDatePickerState(initialSelectedDateMillis = selectedStartDate)
val endDatePickerState = rememberDatePickerState(initialSelectedDateMillis = selectedEndDate)
var isDatesEnabled by rememberSaveable { mutableStateOf(false) }
@ -192,13 +213,14 @@ fun SavingsTransactionFilterDialogContent(
}
Column(
modifier = Modifier.scrollable(state = scrollState, orientation = Orientation.Vertical)
modifier = modifier
.scrollable(state = scrollState, orientation = Orientation.Vertical),
) {
SavingsTransactionRadioFilter.entries.forEach { filter ->
MifosRadioButton(
selected = radioFilter == filter,
onClick = { selectRadioFilter(filter) },
textResId = filter.textResId
textResId = filter.textResId,
)
if (filter == SavingsTransactionRadioFilter.DATE) {
@ -207,13 +229,13 @@ fun SavingsTransactionFilterDialogContent(
text = getDateAsStringFromLong(selectedStartDate),
imageVector = MifosIcons.Edit,
enabled = radioFilter == SavingsTransactionRadioFilter.DATE,
onClick = { showStartDatePickerDialog = true }
onClick = { showStartDatePickerDialog = true },
)
MifosIconTextButton(
text = getDateAsStringFromLong(selectedEndDate),
imageVector = MifosIcons.Edit,
enabled = radioFilter == SavingsTransactionRadioFilter.DATE,
onClick = { showEndDatePickerDialog = true }
onClick = { showEndDatePickerDialog = true },
)
}
}
@ -221,7 +243,7 @@ fun SavingsTransactionFilterDialogContent(
SavingsTransactionCheckBoxFilter.entries.forEach { filter ->
MifosCheckBox(
checked = when(filter) {
checked = when (filter) {
SavingsTransactionCheckBoxFilter.DEPOSIT -> isDepositChecked
SavingsTransactionCheckBoxFilter.DIVIDEND_PAYOUT -> isDividendPayoutChecked
SavingsTransactionCheckBoxFilter.WITHDRAWAL -> isWithdrawalChecked
@ -235,7 +257,7 @@ fun SavingsTransactionFilterDialogContent(
checkedBorderColor = filter.checkBoxColor,
uncheckedBorderColor = filter.checkBoxColor,
checkedBoxColor = filter.checkBoxColor,
)
),
)
}
}
@ -244,8 +266,8 @@ fun SavingsTransactionFilterDialogContent(
DatePickerDialog(
onDismissRequest = { showStartDatePickerDialog = false },
confirmButton = {
startDatePickerState.selectedDateMillis?.let{ setStartDate(it) }
}
startDatePickerState.selectedDateMillis?.let { setStartDate(it) }
},
) { DatePicker(state = startDatePickerState) }
}
@ -254,25 +276,24 @@ fun SavingsTransactionFilterDialogContent(
onDismissRequest = { showEndDatePickerDialog = false },
confirmButton = {
endDatePickerState.selectedDateMillis?.let { setEndDate(it) }
}
},
) { DatePicker(state = endDatePickerState) }
}
}
@Preview
@DevicePreviews
@Composable
fun SavingsTransactionFilterDialogPreview() {
private fun SavingsTransactionFilterDialogPreview() {
MifosMobileTheme {
SavingsTransactionFilterDialog(
savingsTransactionFilterDataModel = SavingsTransactionFilterDataModel(
radioFilter = null,
checkBoxFilters = mutableListOf(),
startDate = Instant.now().toEpochMilli(),
endDate = Instant.now().toEpochMilli()
endDate = Instant.now().toEpochMilli(),
),
filter = {},
onDismiss = {},
)
}
}
}

View File

@ -1,5 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_transaction
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountTransaction
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
@ -8,7 +16,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
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.saveable.rememberSaveable
@ -17,7 +24,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.hilt.navigation.compose.hiltViewModel
@ -31,35 +37,34 @@ import org.mifos.mobile.core.model.entity.accounts.savings.Transactions
import org.mifos.mobile.core.ui.component.EmptyDataView
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
import java.time.Instant
@Composable
fun SavingsAccountTransactionScreen(
viewModel: SavingAccountsTransactionViewModel = hiltViewModel(),
internal fun SavingsAccountTransactionScreen(
navigateBack: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SavingAccountsTransactionViewModel = hiltViewModel(),
) {
val uiState by viewModel.savingAccountsTransactionUiState.collectAsStateWithLifecycle()
val savingsId by viewModel.savingsId.collectAsStateWithLifecycle()
LaunchedEffect(key1 = savingsId) {
viewModel.loadSavingsWithAssociations(accountId = savingsId)
}
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SavingsAccountTransactionScreen(
uiState = uiState,
navigateBack = navigateBack,
retryConnection = { viewModel.loadSavingsWithAssociations(savingsId) },
filterList = { viewModel.filterList(filter = it) }
retryConnection = viewModel::loadSavingsWithAssociations,
filterList = viewModel::filterList,
modifier = modifier,
)
}
@Composable
fun SavingsAccountTransactionScreen(
internal fun SavingsAccountTransactionScreen(
uiState: SavingsAccountTransactionUiState,
navigateBack: () -> Unit,
retryConnection: () -> Unit,
filterList: (SavingsTransactionFilterDataModel) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
var transactionList by rememberSaveable { mutableStateOf(listOf<Transactions>()) }
@ -70,12 +75,13 @@ fun SavingsAccountTransactionScreen(
startDate = Instant.now().toEpochMilli(),
endDate = Instant.now().toEpochMilli(),
radioFilter = null,
checkBoxFilters = mutableListOf()
)
checkBoxFilters = mutableListOf(),
),
)
}
MifosScaffold(
modifier = modifier,
topBar = {
MifosTopBar(
navigateBack = navigateBack,
@ -83,7 +89,7 @@ fun SavingsAccountTransactionScreen(
Text(
text = stringResource(id = R.string.savings_account_transaction),
overflow = TextOverflow.Ellipsis,
maxLines = 1
maxLines = 1,
)
},
actions = {
@ -91,10 +97,10 @@ fun SavingsAccountTransactionScreen(
Icon(
imageVector = MifosIcons.FilterList,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface
tint = MaterialTheme.colorScheme.onSurface,
)
}
}
},
)
},
content = { paddingValues ->
@ -109,7 +115,7 @@ fun SavingsAccountTransactionScreen(
isNetworkConnected = Network.isConnected(context),
isEmptyData = false,
isRetryEnabled = true,
onRetry = retryConnection
onRetry = retryConnection,
)
}
@ -117,7 +123,7 @@ fun SavingsAccountTransactionScreen(
if (uiState.savingAccountsTransactionList.isNullOrEmpty()) {
EmptyDataView(
icon = R.drawable.ic_compare_arrows_black_24dp,
error = R.string.no_transaction_found
error = R.string.no_transaction_found,
)
} else {
transactionList = uiState.savingAccountsTransactionList
@ -126,7 +132,7 @@ fun SavingsAccountTransactionScreen(
}
}
}
}
},
)
if (isDialogOpen) {
@ -141,7 +147,7 @@ fun SavingsAccountTransactionScreen(
}
}
class SavingsAccountTransactionUiStatesParameterProvider :
internal class SavingsAccountTransactionUiStatesParameterProvider :
PreviewParameterProvider<SavingsAccountTransactionUiState> {
override val values: Sequence<SavingsAccountTransactionUiState>
get() = sequenceOf(
@ -151,17 +157,18 @@ class SavingsAccountTransactionUiStatesParameterProvider :
)
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsAccountTransactionScreenPreview(
@PreviewParameter(SavingsAccountTransactionUiStatesParameterProvider::class) savingsAccountUiState: SavingsAccountTransactionUiState
private fun SavingsAccountTransactionScreenPreview(
@PreviewParameter(SavingsAccountTransactionUiStatesParameterProvider::class)
savingsAccountUiState: SavingsAccountTransactionUiState,
) {
MifosMobileTheme {
SavingsAccountTransactionScreen(
uiState = savingsAccountUiState,
navigateBack = { },
retryConnection = { },
filterList = { }
filterList = { },
)
}
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_transaction
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountTransaction
import android.os.Parcelable
import androidx.compose.ui.graphics.Color
@ -8,6 +17,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
@ -20,40 +30,42 @@ import org.mifos.mobile.core.designsystem.theme.RedLight
import org.mifos.mobile.core.model.entity.accounts.savings.TransactionType
import org.mifos.mobile.core.model.entity.accounts.savings.Transactions
import org.mifos.mobile.feature.savings.R
import org.mifos.mobile.feature.savings.savingsAccountTransaction.SavingsAccountTransactionUiState.Loading
import javax.inject.Inject
@HiltViewModel
class SavingAccountsTransactionViewModel @Inject constructor(
internal class SavingAccountsTransactionViewModel @Inject constructor(
private val savingsAccountRepositoryImp: SavingsAccountRepository,
savedStateHandle: SavedStateHandle
) :
ViewModel() {
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val _savingAccountsTransactionUiState = MutableStateFlow<SavingsAccountTransactionUiState>(
SavingsAccountTransactionUiState.Loading
)
val savingAccountsTransactionUiState: StateFlow<SavingsAccountTransactionUiState> get() = _savingAccountsTransactionUiState
private val mUiState = MutableStateFlow<SavingsAccountTransactionUiState>(Loading)
val uiState = mUiState.asStateFlow()
private var _transactionsList: List<Transactions> = mutableListOf()
private val transactionsList: List<Transactions> get() = _transactionsList
val savingsId: StateFlow<Long> = savedStateHandle.getStateFlow<Long>(
private val savingsId: StateFlow<Long> = savedStateHandle.getStateFlow(
key = SAVINGS_ID,
initialValue = -1L
initialValue = -1L,
)
fun loadSavingsWithAssociations(accountId: Long) {
init {
loadSavingsWithAssociations()
}
fun loadSavingsWithAssociations(accountId: Long = savingsId.value) {
viewModelScope.launch {
_savingAccountsTransactionUiState.value = SavingsAccountTransactionUiState.Loading
mUiState.value = Loading
savingsAccountRepositoryImp.getSavingsWithAssociations(
accountId,
org.mifos.mobile.core.common.Constants.TRANSACTIONS,
).catch {
_savingAccountsTransactionUiState.value =
mUiState.value =
SavingsAccountTransactionUiState.Error(it.message)
}.collect {
_transactionsList = it.transactions
_savingAccountsTransactionUiState.value =
mUiState.value =
SavingsAccountTransactionUiState.Success(it.transactions)
}
}
@ -65,55 +77,66 @@ class SavingAccountsTransactionViewModel @Inject constructor(
filterByDateAndType(
startDate = filter.startDate,
endDate = filter.endDate,
checkBoxFilters = filter.checkBoxFilters
checkBoxFilters = filter.checkBoxFilters,
)
}
filter.radioFilter != null -> {
filterByDate(
startDate = filter.startDate,
endDate = filter.endDate
endDate = filter.endDate,
)
}
filter.checkBoxFilters.isNotEmpty() -> {
filterByType(
checkBoxFilters = filter.checkBoxFilters
checkBoxFilters = filter.checkBoxFilters,
)
}
else -> {
_savingAccountsTransactionUiState.value =
mUiState.value =
SavingsAccountTransactionUiState.Success(transactionsList)
}
}
}
private fun filterByDateAndType(
startDate: Long,
endDate: Long,
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>,
) {
val typeFilteredList = filterSavingsAccountTransactionsByType(checkBoxFilters = checkBoxFilters)
val dateAndTypeFilteredList = filterTransactionListByDate(transactions = typeFilteredList, startDate = startDate, endDate = endDate)
_savingAccountsTransactionUiState.value =
val typeFilteredList =
filterSavingsAccountTransactionsByType(checkBoxFilters = checkBoxFilters)
val dateAndTypeFilteredList = filterTransactionListByDate(
transactions = typeFilteredList,
startDate = startDate,
endDate = endDate,
)
mUiState.value =
SavingsAccountTransactionUiState.Success(dateAndTypeFilteredList)
}
private fun filterByDate(startDate: Long, endDate: Long) {
val list = filterTransactionListByDate(transactions = transactionsList, startDate = startDate, endDate = endDate)
_savingAccountsTransactionUiState.value = SavingsAccountTransactionUiState.Success(list)
val list = filterTransactionListByDate(
transactions = transactionsList,
startDate = startDate,
endDate = endDate,
)
mUiState.value = SavingsAccountTransactionUiState.Success(list)
}
private fun filterByType(
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>,
) {
val list = filterSavingsAccountTransactionsByType(checkBoxFilters = checkBoxFilters)
_savingAccountsTransactionUiState.value = SavingsAccountTransactionUiState.Success(list)
mUiState.value = SavingsAccountTransactionUiState.Success(list)
}
private fun filterTransactionListByDate(
transactions: List<Transactions>,
startDate: Long,
endDate: Long
endDate: Long,
): List<Transactions> {
return transactions.filter {
(DateHelper.getDateAsLongFromList(it.date) in startDate..endDate)
@ -121,7 +144,7 @@ class SavingAccountsTransactionViewModel @Inject constructor(
}
private fun filterSavingsAccountTransactionsByType(
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>
checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>,
): List<Transactions> {
var filteredSavingsTransactions: List<Transactions> = ArrayList()
checkBoxFilters.forEach { filter ->
@ -129,12 +152,15 @@ class SavingAccountsTransactionViewModel @Inject constructor(
SavingsTransactionCheckBoxFilter.DEPOSIT -> {
transactionsList.filter { it.transactionType?.deposit == true }
}
SavingsTransactionCheckBoxFilter.DIVIDEND_PAYOUT -> {
transactionsList.filter { it.transactionType?.dividendPayout == true }
}
SavingsTransactionCheckBoxFilter.WITHDRAWAL -> {
transactionsList.filter { it.transactionType?.withdrawal == true }
}
SavingsTransactionCheckBoxFilter.INTEREST_POSTING -> {
transactionsList.filter { it.transactionType?.interestPosting == true }
}
@ -145,13 +171,14 @@ class SavingAccountsTransactionViewModel @Inject constructor(
}
}
sealed class SavingsAccountTransactionUiState {
internal sealed class SavingsAccountTransactionUiState {
data object Loading : SavingsAccountTransactionUiState()
data class Error(val errorMessage: String?) : SavingsAccountTransactionUiState()
data class Success(val savingAccountsTransactionList: List<Transactions>?) : SavingsAccountTransactionUiState()
data class Success(val savingAccountsTransactionList: List<Transactions>?) :
SavingsAccountTransactionUiState()
}
fun getTransactionTriangleResId(transactionType: TransactionType?): Int {
internal fun getTransactionTriangleResId(transactionType: TransactionType?): Int {
return transactionType?.run {
when {
deposit == true -> R.drawable.triangular_green_view
@ -170,38 +197,38 @@ fun getTransactionTriangleResId(transactionType: TransactionType?): Int {
}
@Parcelize
data class SavingsTransactionFilterDataModel(
internal data class SavingsTransactionFilterDataModel(
val startDate: Long,
val endDate: Long,
val radioFilter: SavingsTransactionRadioFilter?,
val checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>
): Parcelable
val checkBoxFilters: MutableList<SavingsTransactionCheckBoxFilter>,
) : Parcelable
enum class SavingsTransactionRadioFilter(val textResId: Int) {
internal enum class SavingsTransactionRadioFilter(val textResId: Int) {
DATE(textResId = R.string.date),
FOUR_WEEKS(textResId = R.string.four_weeks),
THREE_MONTHS(textResId = R.string.three_months),
SIX_MONTHS(textResId = R.string.six_months)
SIX_MONTHS(textResId = R.string.six_months),
}
enum class SavingsTransactionCheckBoxFilter(
internal enum class SavingsTransactionCheckBoxFilter(
val textResId: Int,
val checkBoxColor: Color,
) {
DEPOSIT(
textResId = R.string.deposit,
checkBoxColor = DepositGreen
checkBoxColor = DepositGreen,
),
DIVIDEND_PAYOUT(
textResId = R.string.dividend_payout,
checkBoxColor = RedLight
checkBoxColor = RedLight,
),
WITHDRAWAL(
textResId = R.string.withdrawal,
checkBoxColor = RedLight
checkBoxColor = RedLight,
),
INTEREST_POSTING(
textResId = R.string.interest_posting,
checkBoxColor = GreenSuccess
)
}
checkBoxColor = GreenSuccess,
),
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_withdraw
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountWithdraw
import android.widget.Toast
import androidx.compose.foundation.background
@ -9,7 +18,6 @@ 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.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -22,7 +30,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
@ -30,6 +37,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.core.common.Network
import org.mifos.mobile.core.common.utils.getTodayFormatted
import org.mifos.mobile.core.designsystem.components.MifosButton
import org.mifos.mobile.core.designsystem.components.MifosOutlinedTextField
import org.mifos.mobile.core.designsystem.components.MifosTopBar
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
@ -37,56 +45,65 @@ import org.mifos.mobile.core.model.entity.accounts.savings.SavingsWithAssociatio
import org.mifos.mobile.core.ui.component.MifosProgressIndicator
import org.mifos.mobile.core.ui.component.MifosTitleDescSingleLineEqual
import org.mifos.mobile.core.ui.component.NoInternet
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsAccountWithdrawScreen(
viewModel: SavingsAccountWithdrawViewModel = hiltViewModel(),
internal fun SavingsAccountWithdrawScreen(
navigateBack: (withdrawSuccess: Boolean) -> Unit,
modifier: Modifier = Modifier,
viewModel: SavingsAccountWithdrawViewModel = hiltViewModel(),
) {
val uiState by viewModel.savingsAccountWithdrawUiState.collectAsStateWithLifecycle()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val savingsWithAssociations by viewModel.savingsWithAssociations.collectAsStateWithLifecycle()
SavingsAccountWithdrawScreen(
uiState = uiState,
savingsWithAssociations = savingsWithAssociations,
navigateBack = navigateBack,
withdraw = { viewModel.submitWithdrawSavingsAccount(it) }
withdraw = viewModel::submitWithdrawSavingsAccount,
modifier = modifier,
)
}
@Composable
fun SavingsAccountWithdrawScreen(
private fun SavingsAccountWithdrawScreen(
uiState: SavingsAccountWithdrawUiState,
savingsWithAssociations: SavingsWithAssociations?,
navigateBack: (withdrawSuccess: Boolean) -> Unit,
withdraw: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
Column(modifier = Modifier.fillMaxSize()) {
Column(modifier = modifier.fillMaxSize()) {
MifosTopBar(
navigateBack = { navigateBack(false) },
title = { Text(text = stringResource(id = R.string.withdraw_savings_account)) }
title = { Text(text = stringResource(id = R.string.withdraw_savings_account)) },
)
Box(modifier= Modifier.weight(1f)) {
Box(modifier = Modifier.weight(1f)) {
SavingsAccountWithdrawContent(
savingsWithAssociations = savingsWithAssociations,
withdraw = withdraw
withdraw = withdraw,
)
when (uiState) {
is SavingsAccountWithdrawUiState.Loading -> {
MifosProgressIndicator(modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background.copy(alpha = 0.7f)))
MifosProgressIndicator(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background.copy(alpha = 0.7f)),
)
}
is SavingsAccountWithdrawUiState.Success -> {
LaunchedEffect(true) {
Toast.makeText(context, R.string.savings_account_withdraw_successful, Toast.LENGTH_SHORT).show()
Toast.makeText(
context,
R.string.savings_account_withdraw_successful,
Toast.LENGTH_SHORT,
).show()
}
navigateBack(true)
}
@ -98,12 +115,11 @@ fun SavingsAccountWithdrawScreen(
is SavingsAccountWithdrawUiState.WithdrawUiReady -> {}
}
}
}
}
@Composable
fun ErrorComponent(
private fun ErrorComponent(
errorToast: String?,
) {
val context = LocalContext.current
@ -120,31 +136,32 @@ fun ErrorComponent(
}
@Composable
fun SavingsAccountWithdrawContent(
private fun SavingsAccountWithdrawContent(
savingsWithAssociations: SavingsWithAssociations?,
withdraw: (String) -> Unit
withdraw: (String) -> Unit,
modifier: Modifier = Modifier,
) {
var remark by remember { mutableStateOf(TextFieldValue("")) }
var remarkFieldError by remember { mutableStateOf(false) }
Column(
modifier = Modifier
modifier = modifier
.padding(16.dp)
.fillMaxSize()
.fillMaxSize(),
) {
MifosTitleDescSingleLineEqual(
title = stringResource(id = R.string.client_name),
description = savingsWithAssociations?.clientName ?: ""
description = savingsWithAssociations?.clientName ?: "",
)
Spacer(modifier = Modifier.height(8.dp))
MifosTitleDescSingleLineEqual(
title = stringResource(id = R.string.account_number),
description = savingsWithAssociations?.accountNo ?: ""
description = savingsWithAssociations?.accountNo ?: "",
)
Spacer(modifier = Modifier.height(8.dp))
MifosTitleDescSingleLineEqual(
title = stringResource(id = R.string.withdrawal_date),
description = getTodayFormatted()
description = getTodayFormatted(),
)
Spacer(modifier = Modifier.height(16.dp))
MifosOutlinedTextField(
@ -154,15 +171,16 @@ fun SavingsAccountWithdrawContent(
modifier = Modifier.fillMaxWidth(),
supportingText = stringResource(
R.string.error_validation_blank,
stringResource(R.string.remark)
stringResource(R.string.remark),
),
onValueChange = {
remark = it
remarkFieldError = false
}
},
)
Spacer(modifier = Modifier.height(16.dp))
Button(
MifosButton(
textResId = R.string.withdraw_savings_account,
onClick = {
if (remark.text.isEmpty()) {
remarkFieldError = true
@ -170,34 +188,33 @@ fun SavingsAccountWithdrawContent(
withdraw.invoke(remark.text)
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.withdraw_savings_account))
}
modifier = Modifier.fillMaxWidth(),
)
}
}
class UiStatesParameterProvider : PreviewParameterProvider<SavingsAccountWithdrawUiState> {
internal class UiStatesParameterProvider : PreviewParameterProvider<SavingsAccountWithdrawUiState> {
override val values: Sequence<SavingsAccountWithdrawUiState>
get() = sequenceOf(
SavingsAccountWithdrawUiState.WithdrawUiReady,
SavingsAccountWithdrawUiState.Error(message = ""),
SavingsAccountWithdrawUiState.Loading,
SavingsAccountWithdrawUiState.Success
SavingsAccountWithdrawUiState.Success,
)
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsAccountWithdrawScreenPreview(
@PreviewParameter(UiStatesParameterProvider::class) savingsAccountWithdrawUiState: SavingsAccountWithdrawUiState
private fun SavingsAccountWithdrawScreenPreview(
@PreviewParameter(UiStatesParameterProvider::class)
savingsAccountWithdrawUiState: SavingsAccountWithdrawUiState,
) {
MifosMobileTheme {
SavingsAccountWithdrawScreen(
uiState = savingsAccountWithdrawUiState,
savingsWithAssociations = SavingsWithAssociations(
clientName = "Mifos Mobile",
accountNo = "0001"
accountNo = "0001",
),
navigateBack = {},
withdraw = {},

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_account_withdraw
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsAccountWithdraw
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
@ -7,6 +16,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
@ -15,33 +25,34 @@ import org.mifos.mobile.core.common.Constants
import org.mifos.mobile.core.common.utils.getTodayFormatted
import org.mifos.mobile.core.data.repository.SavingsAccountRepository
import org.mifos.mobile.core.model.entity.accounts.savings.SavingsAccountWithdrawPayload
import org.mifos.mobile.feature.savings.savingsAccountWithdraw.SavingsAccountWithdrawUiState.WithdrawUiReady
import javax.inject.Inject
@HiltViewModel
class SavingsAccountWithdrawViewModel @Inject constructor(
internal class SavingsAccountWithdrawViewModel @Inject constructor(
private val savingsAccountRepositoryImp: SavingsAccountRepository,
savedStateHandle: SavedStateHandle
savedStateHandle: SavedStateHandle,
) : ViewModel() {
val savingsAccountWithdrawUiState: StateFlow<SavingsAccountWithdrawUiState> get() = _savingsAccountWithdrawUiState
private val _savingsAccountWithdrawUiState = MutableStateFlow<SavingsAccountWithdrawUiState>(
SavingsAccountWithdrawUiState.WithdrawUiReady
)
val savingsId: StateFlow<Long> = savedStateHandle.getStateFlow<Long>(
private val mUiState = MutableStateFlow<SavingsAccountWithdrawUiState>(WithdrawUiReady)
val uiState = mUiState.asStateFlow()
private val savingsId: StateFlow<Long> = savedStateHandle.getStateFlow(
key = Constants.SAVINGS_ID,
initialValue = -1L
initialValue = -1L,
)
val savingsWithAssociations = savingsId
.flatMapLatest {
savingsAccountRepositoryImp.getSavingsWithAssociations(
savingsId.value, Constants.TRANSACTIONS,
savingsId.value,
Constants.TRANSACTIONS,
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null
initialValue = null,
)
fun submitWithdrawSavingsAccount(remark: String) {
@ -50,22 +61,25 @@ class SavingsAccountWithdrawViewModel @Inject constructor(
payload.withdrawnOnDate = getTodayFormatted()
viewModelScope.launch {
_savingsAccountWithdrawUiState.value = SavingsAccountWithdrawUiState.Loading
savingsAccountRepositoryImp.submitWithdrawSavingsAccount(savingsWithAssociations.value?.accountNo, payload)
mUiState.value = SavingsAccountWithdrawUiState.Loading
savingsAccountRepositoryImp.submitWithdrawSavingsAccount(
savingsWithAssociations.value?.accountNo,
payload,
)
.catch { e ->
_savingsAccountWithdrawUiState.value =
mUiState.value =
SavingsAccountWithdrawUiState.Error(e.message)
}.collect {
_savingsAccountWithdrawUiState.value =
mUiState.value =
SavingsAccountWithdrawUiState.Success
}
}
}
}
sealed class SavingsAccountWithdrawUiState {
data object WithdrawUiReady: SavingsAccountWithdrawUiState()
data object Loading: SavingsAccountWithdrawUiState()
data object Success: SavingsAccountWithdrawUiState()
data class Error(val message: String?): SavingsAccountWithdrawUiState()
internal sealed class SavingsAccountWithdrawUiState {
data object WithdrawUiReady : SavingsAccountWithdrawUiState()
data object Loading : SavingsAccountWithdrawUiState()
data object Success : SavingsAccountWithdrawUiState()
data class Error(val message: String?) : SavingsAccountWithdrawUiState()
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_make_transfer
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsMakeTransfer
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -9,9 +18,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
@ -26,13 +33,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.mifos.mobile.core.designsystem.components.MifosButton
import org.mifos.mobile.core.designsystem.components.MifosOutlinedButton
import org.mifos.mobile.core.designsystem.components.MifosOutlinedTextField
import org.mifos.mobile.core.designsystem.theme.DarkGray
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
@ -43,13 +50,15 @@ import org.mifos.mobile.core.ui.component.MFStepProcess
import org.mifos.mobile.core.ui.component.MifosDropDownDoubleTextField
import org.mifos.mobile.core.ui.component.StepProcessState
import org.mifos.mobile.core.ui.component.getStepState
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsMakeTransferContent(
internal fun SavingsMakeTransferContent(
uiData: SavingsMakeTransferUiData,
reviewTransfer: (ReviewTransferPayload) -> Unit,
modifier: Modifier = Modifier,
onCancelledClicked: () -> Unit = {},
reviewTransfer: (ReviewTransferPayload) -> Unit
) {
val scrollState = rememberScrollState()
@ -82,14 +91,14 @@ fun SavingsMakeTransferContent(
Pair(payToStepState, R.string.one),
Pair(payFromStepState, R.string.two),
Pair(amountStepState, R.string.three),
Pair(remarkStepState, R.string.four)
Pair(remarkStepState, R.string.four),
)
Column(
modifier = Modifier
modifier = modifier
.verticalScroll(scrollState)
.padding(horizontal = 12.dp)
.fillMaxSize()
.fillMaxSize(),
) {
for (step in stepsState) {
MFStepProcess(
@ -97,48 +106,51 @@ fun SavingsMakeTransferContent(
activateColor = Primary,
processState = step.first,
deactivateColor = DarkGray,
isLastStep = step == stepsState.last()
) { modifier ->
isLastStep = step == stepsState.last(),
) { processModifier ->
when (step.second) {
R.string.one -> PayToStepContent(modifier = modifier,
R.string.one -> PayToStepContent(
modifier = processModifier,
processState = payToStepState,
toAccountOptions = uiData.accountOptionsTemplate.fromAccountOptions,
prefilledAccount = payToAccount,
onContinueClick = {
payToAccount = it
currentStep += 1
}
},
)
R.string.two -> PayFromStep(modifier = modifier,
R.string.two -> PayFromStep(
modifier = processModifier,
processState = payFromStepState,
fromAccountOptions = uiData.accountOptionsTemplate.fromAccountOptions,
prefilledAccount = payFromAccount,
onContinueClick = {
payFromAccount = it
currentStep += 1
}
},
)
R.string.three -> EnterAmountStep(modifier = modifier,
R.string.three -> EnterAmountStep(
processState = amountStepState,
outstandingBalance = uiData.outstandingBalance,
onContinueClick = {
amount = it
currentStep += 1
}
},
modifier = processModifier,
outstandingBalance = uiData.outstandingBalance,
)
R.string.four -> RemarkStep(
modifier = modifier,
modifier = processModifier,
processState = remarkStepState,
onContinueClicked = {
remark = it
reviewTransfer(
ReviewTransferPayload(payToAccount, payFromAccount, amount, remark)
ReviewTransferPayload(payToAccount, payFromAccount, amount, remark),
)
},
onCancelledClicked = onCancelledClicked
onCancelledClicked = onCancelledClicked,
)
}
}
@ -147,12 +159,12 @@ fun SavingsMakeTransferContent(
}
@Composable
fun PayToStepContent(
modifier: Modifier,
private fun PayToStepContent(
toAccountOptions: List<AccountOption>,
prefilledAccount: AccountOption?,
processState: StepProcessState,
onContinueClick: (AccountOption) -> Unit,
modifier: Modifier = Modifier,
) {
var payToAccount by rememberSaveable { mutableStateOf(prefilledAccount) }
var payToStepError by rememberSaveable { mutableStateOf(false) }
@ -168,28 +180,31 @@ fun PayToStepContent(
onClick = { index, _ ->
payToAccount = toAccountOptions[index]
payToStepError = false
}
},
)
if (processState == StepProcessState.ACTIVE) {
Button(onClick = {
if (payToAccount == null) payToStepError = true
else onContinueClick(payToAccount ?: AccountOption())
}, content = {
Text(text = stringResource(id = R.string.continue_str))
})
MifosButton(
textResId = R.string.continue_str,
onClick = {
if (payToAccount == null) {
payToStepError = true
} else {
onContinueClick(payToAccount ?: AccountOption())
}
},
)
}
}
}
@Composable
fun PayFromStep(
modifier: Modifier,
private fun PayFromStep(
fromAccountOptions: List<AccountOption>,
prefilledAccount: AccountOption?,
processState: StepProcessState,
onContinueClick: (AccountOption) -> Unit
onContinueClick: (AccountOption) -> Unit,
modifier: Modifier = Modifier,
) {
var payFromAccount by rememberSaveable { mutableStateOf(prefilledAccount) }
var payFromError by rememberSaveable { mutableStateOf(false) }
@ -197,14 +212,14 @@ fun PayFromStep(
Text(
text = stringResource(id = R.string.pay_from),
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
if (processState == StepProcessState.ACTIVE) {
MifosDropDownDoubleTextField(
optionsList = fromAccountOptions.map {
Pair(
it.accountNo ?: "",
it.clientName ?: ""
it.clientName ?: "",
)
},
selectedOption = payFromAccount?.accountNo ?: "",
@ -214,26 +229,29 @@ fun PayFromStep(
onClick = { index, _ ->
payFromAccount = fromAccountOptions[index]
payFromError = false
}
},
)
MifosButton(
textResId = R.string.continue_str,
onClick = {
if (payFromAccount == null) {
payFromError = true
} else {
onContinueClick(payFromAccount ?: AccountOption())
}
},
)
Button(onClick = {
if (payFromAccount == null) payFromError = true
else onContinueClick(payFromAccount ?: AccountOption())
}, content = {
Text(text = stringResource(id = R.string.continue_str))
})
}
}
}
@Composable
fun EnterAmountStep(
modifier: Modifier,
outstandingBalance: Double? = null,
private fun EnterAmountStep(
processState: StepProcessState,
onContinueClick: (String) -> Unit
onContinueClick: (String) -> Unit,
modifier: Modifier = Modifier,
outstandingBalance: Double? = null,
) {
val context = LocalContext.current
var amount by remember { mutableStateOf(TextFieldValue(outstandingBalance?.toString() ?: "")) }
var amountError by rememberSaveable { mutableStateOf<Int?>(null) }
var showAmountError by rememberSaveable { mutableStateOf(false) }
@ -252,7 +270,7 @@ fun EnterAmountStep(
Text(
text = stringResource(id = R.string.amount),
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
if (processState == StepProcessState.ACTIVE) {
MifosOutlinedTextField(
@ -265,25 +283,26 @@ fun EnterAmountStep(
enabled = outstandingBalance == null,
label = R.string.enter_amount,
)
Button(
MifosButton(
textResId = R.string.continue_str,
onClick = {
if(amountError == null) { onContinueClick(amount.text) }
else { showAmountError = true }
if (amountError == null) {
onContinueClick(amount.text)
} else {
showAmountError = true
}
},
content = {
Text(text = stringResource(id = R.string.continue_str))
}
)
}
}
}
@Composable
fun RemarkStep(
modifier: Modifier,
private fun RemarkStep(
processState: StepProcessState,
onContinueClicked: (String) -> Unit,
onCancelledClicked: () -> Unit = {}
modifier: Modifier = Modifier,
onCancelledClicked: () -> Unit = {},
) {
var remark by remember { mutableStateOf(TextFieldValue("")) }
var remarkError by rememberSaveable { mutableStateOf<Int?>(null) }
@ -301,7 +320,7 @@ fun RemarkStep(
Text(
text = stringResource(id = R.string.remark),
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
)
if (processState == StepProcessState.ACTIVE) {
Spacer(modifier = Modifier.height(12.dp))
@ -311,21 +330,21 @@ fun RemarkStep(
isError = showRemarkError,
supportingText = { remarkError?.let { stringResource(id = it) } },
onValueChange = { remark = it },
label = { Text(text = stringResource(id = R.string.remark)) }
label = { Text(text = stringResource(id = R.string.remark)) },
)
Spacer(modifier = Modifier.height(12.dp))
Row {
Button(
MifosButton(
textResId = R.string.review,
onClick = {
remarkError?.let { showRemarkError = true }
?: onContinueClicked(remark.text)
},
content = { Text(text = stringResource(id = R.string.review)) }
)
Spacer(modifier = Modifier.width(12.dp))
OutlinedButton(
onClick = { onCancelledClicked() },
content = { Text(text = stringResource(id = R.string.cancel)) }
MifosOutlinedButton(
textResId = R.string.cancel,
onClick = onCancelledClicked,
)
}
} else {
@ -333,20 +352,19 @@ fun RemarkStep(
text = stringResource(id = R.string.enter_remarks),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.labelMedium
style = MaterialTheme.typography.labelMedium,
)
}
}
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsMakeTransferContentPreview() {
private fun SavingsMakeTransferContentPreview() {
MifosMobileTheme {
SavingsMakeTransferContent(
uiData = SavingsMakeTransferUiData(),
reviewTransfer = {}
reviewTransfer = {},
)
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_make_transfer
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsMakeTransfer
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@ -6,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.hilt.navigation.compose.hiltViewModel
@ -19,14 +27,16 @@ import org.mifos.mobile.core.model.entity.payload.ReviewTransferPayload
import org.mifos.mobile.core.model.enums.TransferType
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay
import org.mifos.mobile.core.ui.utils.DevicePreviews
import org.mifos.mobile.feature.savings.R
@Composable
fun SavingsMakeTransferScreen(
viewModel: SavingsMakeTransferViewModel = hiltViewModel(),
internal fun SavingsMakeTransferScreen(
onCancelledClicked: () -> Unit,
navigateBack: () -> Unit,
reviewTransfer: (ReviewTransferPayload, TransferType) -> Unit
reviewTransfer: (ReviewTransferPayload, TransferType) -> Unit,
modifier: Modifier = Modifier,
viewModel: SavingsMakeTransferViewModel = hiltViewModel(),
) {
val uiState = viewModel.savingsMakeTransferUiState.collectAsStateWithLifecycle()
val uiData = viewModel.savingsMakeTransferUiData.collectAsStateWithLifecycle()
@ -36,36 +46,46 @@ fun SavingsMakeTransferScreen(
onCancelledClicked = onCancelledClicked,
uiState = uiState.value,
uiData = uiData.value,
reviewTransfer = { reviewTransfer(it, TransferType.SELF) }
modifier = modifier,
reviewTransfer = { reviewTransfer(it, TransferType.SELF) },
)
}
@Composable
fun SavingsMakeTransferScreen(
private fun SavingsMakeTransferScreen(
uiState: SavingsMakeTransferUiState,
uiData: SavingsMakeTransferUiData,
onCancelledClicked: () -> Unit = {},
navigateBack: () -> Unit,
reviewTransfer: (ReviewTransferPayload) -> Unit
reviewTransfer: (ReviewTransferPayload) -> Unit,
modifier: Modifier = Modifier,
onCancelledClicked: () -> Unit = {},
) {
val context = LocalContext.current
MifosScaffold(
topBarTitleResId = if(uiData.transferType == Constants.TRANSFER_PAY_TO) R.string.deposit
else R.string.transfer,
topBarTitleResId = if (uiData.transferType == Constants.TRANSFER_PAY_TO) {
R.string.deposit
} else {
R.string.transfer
},
navigateBack = navigateBack,
modifier = modifier,
content = {
Box(modifier = Modifier.padding(it).fillMaxSize()) {
Box(
modifier = Modifier
.padding(it)
.fillMaxSize(),
) {
SavingsMakeTransferContent(
uiData = uiData,
reviewTransfer = reviewTransfer,
onCancelledClicked = onCancelledClicked,
reviewTransfer = reviewTransfer
)
when(uiState) {
when (uiState) {
is SavingsMakeTransferUiState.ShowUI -> Unit
is SavingsMakeTransferUiState.Loading -> { MifosProgressIndicatorOverlay() }
is SavingsMakeTransferUiState.Loading -> MifosProgressIndicatorOverlay()
is SavingsMakeTransferUiState.Error -> {
MifosErrorComponent(
@ -76,13 +96,12 @@ fun SavingsMakeTransferScreen(
}
}
}
}
},
)
}
class SavingsMakeTransferUiStatesPreviews : PreviewParameterProvider<SavingsMakeTransferUiState> {
internal class SavingsMakeTransferUiStatesPreviews :
PreviewParameterProvider<SavingsMakeTransferUiState> {
override val values: Sequence<SavingsMakeTransferUiState>
get() = sequenceOf(
SavingsMakeTransferUiState.ShowUI,
@ -91,18 +110,19 @@ class SavingsMakeTransferUiStatesPreviews : PreviewParameterProvider<SavingsMake
)
}
@Preview(showSystemUi = true)
@DevicePreviews
@Composable
fun SavingsMakeTransferContentPreview(
@PreviewParameter(SavingsMakeTransferUiStatesPreviews::class) savingsMakeTransferUIState: SavingsMakeTransferUiState
private fun SavingsMakeTransferContentPreview(
@PreviewParameter(SavingsMakeTransferUiStatesPreviews::class)
savingsMakeTransferUIState: SavingsMakeTransferUiState,
) {
MifosMobileTheme {
SavingsMakeTransferScreen(
navigateBack = { },
onCancelledClicked = { },
reviewTransfer = { },
navigateBack = { },
onCancelledClicked = { },
reviewTransfer = { },
uiState = savingsMakeTransferUIState,
uiData = SavingsMakeTransferUiData()
uiData = SavingsMakeTransferUiData(),
)
}
}
}

View File

@ -1,4 +1,13 @@
package org.mifos.mobile.feature.savings.savings_make_transfer
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.feature.savings.savingsMakeTransfer
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
@ -19,23 +28,22 @@ import org.mifos.mobile.core.network.Result
import org.mifos.mobile.core.network.asResult
import javax.inject.Inject
@HiltViewModel
class SavingsMakeTransferViewModel @Inject constructor(
internal class SavingsMakeTransferViewModel @Inject constructor(
private val savingsAccountRepositoryImp: SavingsAccountRepository,
savedStateHandle: SavedStateHandle
savedStateHandle: SavedStateHandle,
) : ViewModel() {
val accountId = savedStateHandle.getStateFlow(key = Constants.ACCOUNT_ID, initialValue = -1L)
private val transferType: StateFlow<String> = savedStateHandle.getStateFlow(
key = Constants.TRANSFER_TYPE,
initialValue = TRANSFER_PAY_TO
initialValue = TRANSFER_PAY_TO,
)
private val outstandingBalance: StateFlow<Double?> = savedStateHandle.getStateFlow<String?>(
key = Constants.OUTSTANDING_BALANCE,
initialValue = null
initialValue = null,
).map { balanceString ->
balanceString?.toDoubleOrNull() ?: 0.0
}.stateIn(
@ -54,16 +62,22 @@ class SavingsMakeTransferViewModel @Inject constructor(
.asResult()
.map { result ->
when (result) {
is Result.Success -> SavingsMakeTransferUiState.ShowUI
.also {
_savingsMakeTransferUiData.value = _savingsMakeTransferUiData.value
.copy(
accountOptionsTemplate = result.data,
transferType = transferType.value,
outstandingBalance = if(outstandingBalance.value == 0.0) null else outstandingBalance.value,
accountId = accountId.value
)
}
is Result.Success ->
SavingsMakeTransferUiState.ShowUI
.also {
_savingsMakeTransferUiData.value = _savingsMakeTransferUiData.value
.copy(
accountOptionsTemplate = result.data,
transferType = transferType.value,
outstandingBalance = if (outstandingBalance.value == 0.0) {
null
} else {
outstandingBalance.value
},
accountId = accountId.value,
)
}
is Result.Loading -> SavingsMakeTransferUiState.Loading
is Result.Error -> SavingsMakeTransferUiState.Error(result.exception.message)
}
@ -72,21 +86,19 @@ class SavingsMakeTransferViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000),
initialValue = SavingsMakeTransferUiState.Loading,
)
}
sealed class SavingsMakeTransferUiState {
internal sealed class SavingsMakeTransferUiState {
data object Loading : SavingsMakeTransferUiState()
data class Error(val errorMessage: String?) : SavingsMakeTransferUiState()
data object ShowUI : SavingsMakeTransferUiState()
}
data class SavingsMakeTransferUiData(
internal data class SavingsMakeTransferUiData(
var accountId: Long? = null,
var transferType: String? = null,
var outstandingBalance: Double? = null,
var accountOptionsTemplate: AccountOptionsTemplate = AccountOptionsTemplate(),
var toAccountOptionPrefilled: AccountOption? = null,
var fromAccountOptionPrefilled: AccountOption? = null
var fromAccountOptionPrefilled: AccountOption? = null,
)

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="24dp"

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="24dp"

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<resources>
<string name="active">Active</string>
<string name="need_approval">Need Approval</string>