diff --git a/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/components/MifosButton.kt b/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/components/MifosButton.kt index c68857daa..2871e2a90 100644 --- a/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/components/MifosButton.kt +++ b/core/designsystem/src/main/kotlin/org/mifos/mobile/core/designsystem/components/MifosButton.kt @@ -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, diff --git a/feature/savings/build.gradle.kts b/feature/savings/build.gradle.kts index ad347eee2..9f7d19114 100644 --- a/feature/savings/build.gradle.kts +++ b/feature/savings/build.gradle.kts @@ -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) diff --git a/feature/savings/src/main/AndroidManifest.xml b/feature/savings/src/main/AndroidManifest.xml index a5918e68a..1f9b243f0 100644 --- a/feature/savings/src/main/AndroidManifest.xml +++ b/feature/savings/src/main/AndroidManifest.xml @@ -1,4 +1,13 @@ + \ No newline at end of file diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavGraph.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavGraph.kt index a9faeb615..e0a52e76b 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavGraph.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavGraph.kt @@ -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, ) } } diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavigation.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavigation.kt index d5c6470f7..d7ee0a10c 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavigation.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/navigation/SavingsNavigation.kt @@ -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" } } diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingAccountsDetailViewModel.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingAccountsDetailViewModel.kt similarity index 76% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingAccountsDetailViewModel.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingAccountsDetailViewModel.kt index ac2fe09dc..b21a37fe9 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingAccountsDetailViewModel.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingAccountsDetailViewModel.kt @@ -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(key = Constants.SAVINGS_ID, initialValue = null) + val savingsId = + savedStateHandle.getStateFlow(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 { +internal fun Status.getStatusColorAndText(): Pair { 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 { else -> Pair(Color.Black, R.string.closed) } } - - diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailContent.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailContent.kt similarity index 79% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailContent.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailContent.kt index cdf2b60fc..3d93699ea 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailContent.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailContent.kt @@ -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(), ) } } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailScreen.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailScreen.kt similarity index 64% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailScreen.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailScreen.kt index 73116c258..c4424cf55 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailScreen.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailScreen.kt @@ -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 = {}, ) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailTopBar.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailTopBar.kt similarity index 63% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailTopBar.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailTopBar.kt index 072051417..3a6616432 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account/SavingsAccountDetailTopBar.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccount/SavingsAccountDetailTopBar.kt @@ -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 = {}, + ) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationContent.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationContent.kt similarity index 72% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationContent.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationContent.kt index ee5b7a03c..bc79215a4 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationContent.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationContent.kt @@ -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, + ) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationScreen.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationScreen.kt similarity index 71% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationScreen.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationScreen.kt index 285f431fa..0a822b02a 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationScreen.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationScreen.kt @@ -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, + ) } } - diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationViewModel.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationViewModel.kt similarity index 72% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationViewModel.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationViewModel.kt index ecf8e2cd0..95828b737 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_application/SavingsAccountApplicationViewModel.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountApplication/SavingsAccountApplicationViewModel.kt @@ -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 get() = _savingsAccountApplicationUiState - private val _savingsAccountApplicationUiState = - MutableStateFlow(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 = savingsId + private val _savingsAccountApplicationUiState = + MutableStateFlow(Loading) + val savingsAccountApplicationUiState = _savingsAccountApplicationUiState.asStateFlow() + + private val savingsWithAssociations: StateFlow = 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() } - diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountTransactionContent.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountTransactionContent.kt similarity index 76% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountTransactionContent.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountTransactionContent.kt index 6d22c94e3..27f761e83 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountTransactionContent.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountTransactionContent.kt @@ -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, + 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()) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionFilterDialog.kt similarity index 78% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionFilterDialog.kt index 1a00e2328..2b7fc2d88 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionFilterDialog.kt @@ -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 = {}, ) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionScreen.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionScreen.kt similarity index 80% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionScreen.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionScreen.kt index e495cff5f..3ee8da3e0 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionScreen.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionScreen.kt @@ -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()) } @@ -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 { override val values: Sequence 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 = { }, ) } -} \ No newline at end of file +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionViewModel.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionViewModel.kt similarity index 70% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionViewModel.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionViewModel.kt index 6f7028b73..75b282cc6 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_transaction/SavingAccountsTransactionViewModel.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountTransaction/SavingAccountsTransactionViewModel.kt @@ -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.Loading - ) - val savingAccountsTransactionUiState: StateFlow get() = _savingAccountsTransactionUiState + private val mUiState = MutableStateFlow(Loading) + val uiState = mUiState.asStateFlow() private var _transactionsList: List = mutableListOf() private val transactionsList: List get() = _transactionsList - val savingsId: StateFlow = savedStateHandle.getStateFlow( + private val savingsId: StateFlow = 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 + checkBoxFilters: MutableList, ) { - 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 + checkBoxFilters: MutableList, ) { val list = filterSavingsAccountTransactionsByType(checkBoxFilters = checkBoxFilters) - _savingAccountsTransactionUiState.value = SavingsAccountTransactionUiState.Success(list) + mUiState.value = SavingsAccountTransactionUiState.Success(list) } private fun filterTransactionListByDate( transactions: List, startDate: Long, - endDate: Long + endDate: Long, ): List { return transactions.filter { (DateHelper.getDateAsLongFromList(it.date) in startDate..endDate) @@ -121,7 +144,7 @@ class SavingAccountsTransactionViewModel @Inject constructor( } private fun filterSavingsAccountTransactionsByType( - checkBoxFilters: MutableList + checkBoxFilters: MutableList, ): List { var filteredSavingsTransactions: List = 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?) : SavingsAccountTransactionUiState() + data class Success(val savingAccountsTransactionList: List?) : + 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 -): Parcelable + val checkBoxFilters: MutableList, +) : 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 - ) -} \ No newline at end of file + checkBoxColor = GreenSuccess, + ), +} diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawScreen.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawScreen.kt similarity index 71% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawScreen.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawScreen.kt index d530349fa..9df4624aa 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawScreen.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawScreen.kt @@ -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 { +internal class UiStatesParameterProvider : PreviewParameterProvider { override val values: Sequence 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 = {}, diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawViewModel.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawViewModel.kt similarity index 52% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawViewModel.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawViewModel.kt index f3244e009..f06c4dfd2 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_account_withdraw/SavingsAccountWithdrawViewModel.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsAccountWithdraw/SavingsAccountWithdrawViewModel.kt @@ -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 get() = _savingsAccountWithdrawUiState - private val _savingsAccountWithdrawUiState = MutableStateFlow( - SavingsAccountWithdrawUiState.WithdrawUiReady - ) - val savingsId: StateFlow = savedStateHandle.getStateFlow( + private val mUiState = MutableStateFlow(WithdrawUiReady) + val uiState = mUiState.asStateFlow() + + private val savingsId: StateFlow = 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() } diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferContent.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferContent.kt similarity index 75% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferContent.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferContent.kt index 0a69fe631..ff6a7864f 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferContent.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferContent.kt @@ -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, 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, 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(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(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 = {}, ) } } - diff --git a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferScreen.kt b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferScreen.kt similarity index 65% rename from feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferScreen.kt rename to feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferScreen.kt index 0a62d6afe..20756f820 100644 --- a/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savings_make_transfer/SavingsMakeTransferScreen.kt +++ b/feature/savings/src/main/java/org/mifos/mobile/feature/savings/savingsMakeTransfer/SavingsMakeTransferScreen.kt @@ -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 { +internal class SavingsMakeTransferUiStatesPreviews : + PreviewParameterProvider { override val values: Sequence get() = sequenceOf( SavingsMakeTransferUiState.ShowUI, @@ -91,18 +110,19 @@ class SavingsMakeTransferUiStatesPreviews : PreviewParameterProvider = savedStateHandle.getStateFlow( key = Constants.TRANSFER_TYPE, - initialValue = TRANSFER_PAY_TO + initialValue = TRANSFER_PAY_TO, ) private val outstandingBalance: StateFlow = savedStateHandle.getStateFlow( 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, ) - diff --git a/feature/savings/src/main/res/drawable/ic_assignment_turned_in_black_24dp.xml b/feature/savings/src/main/res/drawable/ic_assignment_turned_in_black_24dp.xml index 7aeaab097..e0a066387 100644 --- a/feature/savings/src/main/res/drawable/ic_assignment_turned_in_black_24dp.xml +++ b/feature/savings/src/main/res/drawable/ic_assignment_turned_in_black_24dp.xml @@ -1,3 +1,13 @@ + + + + + + + + Active Need Approval