From ba7ae9da6b02d95bfc0391788f649a5dd478e89b Mon Sep 17 00:00:00 2001 From: Naveen Kumar Kuppan Date: Sun, 2 Jun 2024 00:17:33 +0200 Subject: [PATCH] Converting the account list re-order to access via state and action --- .../account/list/AccountListScreenKtTest.kt | 37 ++++++------ .../account/reorder/AccountReOrderAction.kt | 10 ++++ .../account/reorder/AccountReOrderScreen.kt | 56 ++++++++++--------- .../account/reorder/AccountReOrderState.kt | 8 +++ .../reorder/AccountReOrderViewModel.kt | 39 ++++++++----- .../category/details/CategoryDetailScreen.kt | 11 ++-- .../details/CategoryDetailViewModel.kt | 47 ++++++++++------ .../category/details/CategoryDetailsState.kt | 9 +++ 8 files changed, 133 insertions(+), 84 deletions(-) create mode 100644 feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderAction.kt create mode 100644 feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderState.kt create mode 100644 feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailsState.kt diff --git a/feature/account/src/androidTest/kotlin/com/naveenapps/expensemanager/feature/account/list/AccountListScreenKtTest.kt b/feature/account/src/androidTest/kotlin/com/naveenapps/expensemanager/feature/account/list/AccountListScreenKtTest.kt index 561f18e9..bee4953f 100644 --- a/feature/account/src/androidTest/kotlin/com/naveenapps/expensemanager/feature/account/list/AccountListScreenKtTest.kt +++ b/feature/account/src/androidTest/kotlin/com/naveenapps/expensemanager/feature/account/list/AccountListScreenKtTest.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.naveenapps.expensemanager.core.common.utils.UiState import com.naveenapps.expensemanager.core.testing.TestMainActivity import org.junit.Rule import org.junit.Test @@ -20,15 +19,15 @@ class AccountListScreenKtTest { val composeTestRule = createAndroidComposeRule() @Test - fun openAccountListScreenAndClickCreateButton() { + fun openAccountListScreenAndClickCreateButton() { // Start the app composeTestRule.setContent { AccountListContentView( - state = UiState.Success(emptyList()), - onAction = false, - openAccountReOrderScreen = {}, - closePage = {}, - openCreateScreen = {} + state = AccountListState( + accounts = emptyList(), + showReOrder = false + ), + onAction = { }, ) } @@ -36,14 +35,14 @@ class AccountListScreenKtTest { } @Test - fun openAccountListScreenWithDataShouldShowAccountList() { + fun openAccountListScreenWithDataShouldShowAccountList() { composeTestRule.setContent { AccountListContentView( - state = UiState.Success(getRandomAccountUiModel(5)), - onAction = true, - openAccountReOrderScreen = {}, - closePage = {}, - openCreateScreen = {} + state = AccountListState( + accounts = getRandomAccountUiModel(5), + showReOrder = false + ), + onAction = { }, ) } @@ -52,14 +51,14 @@ class AccountListScreenKtTest { } @Test - fun showAccountListEmptyStateWhenNoItemsAvailable() { + fun showAccountListEmptyStateWhenNoItemsAvailable() { composeTestRule.setContent { AccountListContentView( - state = UiState.Empty, - onAction = true, - openAccountReOrderScreen = {}, - closePage = {}, - openCreateScreen = {} + state = AccountListState( + accounts = emptyList(), + showReOrder = false + ), + onAction = { }, ) } diff --git a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderAction.kt b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderAction.kt new file mode 100644 index 00000000..f885b9d2 --- /dev/null +++ b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderAction.kt @@ -0,0 +1,10 @@ +package com.naveenapps.expensemanager.feature.account.reorder + +sealed class AccountReOrderAction { + + data object ClosePage : AccountReOrderAction() + + data object Save : AccountReOrderAction() + + data class Swap(val fromIndex: Int, val toIndex: Int) : AccountReOrderAction() +} \ No newline at end of file diff --git a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderScreen.kt b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderScreen.kt index 5432ae1b..e07c63af 100644 --- a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderScreen.kt +++ b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.outlined.Reorder import androidx.compose.material3.FloatingActionButton @@ -32,8 +33,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.naveenapps.expensemanager.core.designsystem.AppPreviewsLightAndDarkMode import com.naveenapps.expensemanager.core.designsystem.components.dragGestureHandler import com.naveenapps.expensemanager.core.designsystem.components.rememberDragDropListState +import com.naveenapps.expensemanager.core.designsystem.ui.components.AppTopNavigationBar import com.naveenapps.expensemanager.core.designsystem.ui.components.IconAndBackgroundView -import com.naveenapps.expensemanager.core.designsystem.ui.components.TopNavigationBar import com.naveenapps.expensemanager.core.designsystem.ui.theme.ExpenseManagerTheme import com.naveenapps.expensemanager.core.designsystem.ui.utils.ItemSpecModifier import com.naveenapps.expensemanager.core.model.Account @@ -45,25 +46,18 @@ import kotlinx.coroutines.Job fun AccountReOrderScreen( viewModel: AccountReOrderViewModel = hiltViewModel(), ) { - val accounts by viewModel.accounts.collectAsState() - val showActionButton by viewModel.showActionButton.collectAsState() + val state by viewModel.state.collectAsState() AccountReOrderScaffoldView( - accounts = accounts, - showActionButton = showActionButton, - backPress = viewModel::closePage, - saveChanges = viewModel::saveChanges, - onMove = viewModel::swap, + state = state, + onAction = viewModel::processAction, ) } @Composable private fun AccountReOrderScaffoldView( - accounts: List, - showActionButton: Boolean, - onMove: (Int, Int) -> Unit, - saveChanges: () -> Unit, - backPress: () -> Unit, + state: AccountReOrderState, + onAction: (AccountReOrderAction) -> Unit, ) { val snackbarHostState = remember { SnackbarHostState() } @@ -72,14 +66,21 @@ private fun AccountReOrderScaffoldView( SnackbarHost(hostState = snackbarHostState) }, topBar = { - TopNavigationBar( + AppTopNavigationBar( title = stringResource(R.string.accounts_re_order), - onClick = backPress, + navigationIcon = Icons.AutoMirrored.Filled.ArrowBack, + navigationBackClick = { + onAction.invoke(AccountReOrderAction.ClosePage) + }, ) }, floatingActionButton = { - if (showActionButton) { - FloatingActionButton(onClick = saveChanges) { + if (state.showSaveButton) { + FloatingActionButton( + onClick = { + onAction.invoke(AccountReOrderAction.Save) + } + ) { Icon( imageVector = Icons.Default.Done, contentDescription = "", @@ -89,8 +90,12 @@ private fun AccountReOrderScaffoldView( }, ) { innerPadding -> ReOrderContent( - accounts = accounts, - onMove = onMove, + accounts = state.accounts, + onMove = { from, to -> + onAction.invoke( + AccountReOrderAction.Swap(from, to) + ) + }, modifier = Modifier .padding(innerPadding) .fillMaxSize(), @@ -179,12 +184,11 @@ fun AccountReOrderItem( fun AccountReOrderScaffoldViewPreview() { ExpenseManagerTheme { AccountReOrderScaffoldView( - accounts = getRandomAccountData(5), - showActionButton = true, - onMove = { i, j -> - - }, - saveChanges = {}, - backPress = {}) + state = AccountReOrderState( + accounts = getRandomAccountData(5), + showSaveButton = true + ), + onAction = {}, + ) } } diff --git a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderState.kt b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderState.kt new file mode 100644 index 00000000..2f3dbbed --- /dev/null +++ b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderState.kt @@ -0,0 +1,8 @@ +package com.naveenapps.expensemanager.feature.account.reorder + +import com.naveenapps.expensemanager.core.model.Account + +data class AccountReOrderState( + val accounts: List, + val showSaveButton: Boolean +) \ No newline at end of file diff --git a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderViewModel.kt b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderViewModel.kt index da038e6e..d77aba11 100644 --- a/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderViewModel.kt +++ b/feature/account/src/main/kotlin/com/naveenapps/expensemanager/feature/account/reorder/AccountReOrderViewModel.kt @@ -5,13 +5,13 @@ import androidx.lifecycle.viewModelScope import com.naveenapps.expensemanager.core.designsystem.components.swap import com.naveenapps.expensemanager.core.domain.usecase.account.GetAllAccountsUseCase import com.naveenapps.expensemanager.core.domain.usecase.account.UpdateAllAccountUseCase -import com.naveenapps.expensemanager.core.model.Account import com.naveenapps.expensemanager.core.navigation.AppComposeNavigator import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @@ -22,26 +22,28 @@ class AccountReOrderViewModel @Inject constructor( private val appComposeNavigator: AppComposeNavigator, ) : ViewModel() { - private val _showActionButton = MutableStateFlow(false) - val showActionButton = _showActionButton.asStateFlow() - - private val _accounts = MutableStateFlow>(emptyList()) - val accounts = _accounts.asStateFlow() + private val _state = MutableStateFlow( + AccountReOrderState( + accounts = emptyList(), + showSaveButton = false + ) + ) + val state = _state.asStateFlow() init { - getAllAccountsUseCase.invoke().onEach { - _accounts.value = it + getAllAccountsUseCase.invoke().onEach { accounts -> + _state.update { it.copy(accounts = accounts, showSaveButton = false) } }.launchIn(viewModelScope) } - fun closePage() { + private fun closePage() { appComposeNavigator.popBackStack() } - fun saveChanges() { + private fun saveChanges() { viewModelScope.launch { updateAllAccountUseCase.invoke( - _accounts.value.mapIndexed { index, item -> + _state.value.accounts.mapIndexed { index, item -> item.copy(sequence = index) }, ) @@ -50,12 +52,19 @@ class AccountReOrderViewModel @Inject constructor( } } - fun swap(fromIndex: Int, toIndex: Int) { + private fun swap(fromIndex: Int, toIndex: Int) { viewModelScope.launch { - val updatedPomodoroList = _accounts.value.toMutableList() + val updatedPomodoroList = _state.value.accounts.toMutableList() val swappedList = updatedPomodoroList.swap(fromIndex, toIndex) - _accounts.value = swappedList - _showActionButton.value = true + _state.update { it.copy(accounts = swappedList, showSaveButton = true) } + } + } + + fun processAction(action: AccountReOrderAction) { + when (action) { + AccountReOrderAction.ClosePage -> closePage() + AccountReOrderAction.Save -> saveChanges() + is AccountReOrderAction.Swap -> swap(action.fromIndex, action.toIndex) } } } diff --git a/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailScreen.kt b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailScreen.kt index 909265b5..67192ca9 100644 --- a/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailScreen.kt +++ b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailScreen.kt @@ -55,8 +55,7 @@ import com.naveenapps.expensemanager.feature.category.transaction.CategoryTransa fun CategoryDetailScreen( viewModel: CategoryDetailViewModel = hiltViewModel(), ) { - val category by viewModel.category.collectAsState() - val categoryTransactions by viewModel.categoryTransactions.collectAsState() + val state by viewModel.state.collectAsState() val snackbarHostState = remember { SnackbarHostState() } @@ -79,7 +78,7 @@ fun CategoryDetailScreen( title = { }, actions = { - category?.let { + state.categoryTransaction?.let { IconButton(onClick = { viewModel.openCategoryEditScreen() }) { @@ -91,7 +90,7 @@ fun CategoryDetailScreen( } }, ) - category?.let { categoryTransaction -> + state.categoryTransaction?.let { categoryTransaction -> CategoryTransactionItem( modifier = Modifier.padding(16.dp), name = categoryTransaction.category.name, @@ -121,7 +120,7 @@ fun CategoryDetailScreen( .padding(innerPadding) .fillMaxSize(), ) { - if (categoryTransactions.isEmpty()) { + if (state.transactions.isEmpty()) { Text( modifier = Modifier .wrapContentSize() @@ -131,7 +130,7 @@ fun CategoryDetailScreen( ) } else { Column(modifier = Modifier.padding(top = 16.dp)) { - categoryTransactions.forEach { item -> + state.transactions.forEach { item -> TransactionItem( categoryName = item.categoryName, fromAccountName = item.fromAccountName, diff --git a/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailViewModel.kt b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailViewModel.kt index 0c8a4c3c..a08e7b2a 100644 --- a/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailViewModel.kt +++ b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailViewModel.kt @@ -8,7 +8,6 @@ import com.naveenapps.expensemanager.core.domain.usecase.settings.currency.GetCu import com.naveenapps.expensemanager.core.domain.usecase.settings.currency.GetFormattedAmountUseCase import com.naveenapps.expensemanager.core.domain.usecase.transaction.GetTransactionWithFilterUseCase import com.naveenapps.expensemanager.core.model.CategoryTransaction -import com.naveenapps.expensemanager.core.model.TransactionUiItem import com.naveenapps.expensemanager.core.model.toTransactionUIModel import com.naveenapps.expensemanager.core.navigation.AppComposeNavigator import com.naveenapps.expensemanager.core.navigation.ExpenseManagerArgsNames @@ -18,6 +17,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.update import javax.inject.Inject @HiltViewModel @@ -30,11 +30,13 @@ class CategoryDetailViewModel @Inject constructor( private val appComposeNavigator: AppComposeNavigator, ) : ViewModel() { - private val _category = MutableStateFlow(null) - val category = _category.asStateFlow() - - private val _categoryTransactions = MutableStateFlow>(emptyList()) - val categoryTransactions = _categoryTransactions.asStateFlow() + private val _state = MutableStateFlow( + CategoryDetailsState( + categoryTransaction = null, + transactions = emptyList() + ) + ) + val state = _state.asStateFlow() init { savedStateHandle.get(ExpenseManagerArgsNames.ID)?.let { @@ -63,19 +65,28 @@ class CategoryDetailViewModel @Inject constructor( val categoryAmount = categoryTransaction.sumOf { it.amount.amount } - _category.value = CategoryTransaction( - category = category, - percent = (categoryAmount / totalSpent).toFloat() * 100, - amount = getFormattedAmountUseCase.invoke( - amount = categoryAmount, - currency = currency, - ), - transaction = filterTransaction, - ) - _categoryTransactions.value = categoryTransaction + + _state.update { + it.copy( + categoryTransaction = CategoryTransaction( + category = category, + percent = (categoryAmount / totalSpent).toFloat() * 100, + amount = getFormattedAmountUseCase.invoke( + amount = categoryAmount, + currency = currency, + ), + transaction = filterTransaction, + ), + transactions = categoryTransaction + ) + } } else { - _category.value = null - _categoryTransactions.value = emptyList() + _state.update { + it.copy( + categoryTransaction = null, + transactions = emptyList() + ) + } } }.launchIn(viewModelScope) } diff --git a/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailsState.kt b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailsState.kt new file mode 100644 index 00000000..effca6fe --- /dev/null +++ b/feature/category/src/main/kotlin/com/naveenapps/expensemanager/feature/category/details/CategoryDetailsState.kt @@ -0,0 +1,9 @@ +package com.naveenapps.expensemanager.feature.category.details + +import com.naveenapps.expensemanager.core.model.CategoryTransaction +import com.naveenapps.expensemanager.core.model.TransactionUiItem + +data class CategoryDetailsState( + val categoryTransaction: CategoryTransaction?, + val transactions: List +) \ No newline at end of file