From b1aabdf94fea90597c73bbbafcd4a29e7da22d5a Mon Sep 17 00:00:00 2001 From: Avneet Singh Date: Tue, 27 Feb 2024 05:01:12 +0530 Subject: [PATCH] refactor #2521: migrated savings account application screen to compose --- .../SavingsAccountApplicationActivity.kt | 2 +- .../SavingsAccountApplicationFragment.kt | 238 ------------------ .../SavingAccountsDetailFragment.kt | 2 +- .../SavingsAccountApplicationContent.kt | 183 ++++++++++++++ .../SavingsAccountApplicationFragment.kt | 75 ++++++ .../SavingsAccountApplicationScreen.kt | 109 ++++++++ .../SavingsAccountApplicationViewModel.kt | 127 ++++++++++ .../SavingsAccountApplicationViewModel.kt | 74 ------ .../SavingsAccountApplicationViewModelTest.kt | 1 + .../mobile/core/ui/component/EmptyDataView.kt | 6 +- 10 files changed, 501 insertions(+), 316 deletions(-) delete mode 100644 app/src/main/java/org/mifos/mobile/ui/fragments/SavingsAccountApplicationFragment.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationContent.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationFragment.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationScreen.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationViewModel.kt delete mode 100644 app/src/main/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModel.kt diff --git a/app/src/main/java/org/mifos/mobile/ui/activities/SavingsAccountApplicationActivity.kt b/app/src/main/java/org/mifos/mobile/ui/activities/SavingsAccountApplicationActivity.kt index 111949865..44112fdd1 100644 --- a/app/src/main/java/org/mifos/mobile/ui/activities/SavingsAccountApplicationActivity.kt +++ b/app/src/main/java/org/mifos/mobile/ui/activities/SavingsAccountApplicationActivity.kt @@ -5,7 +5,7 @@ import org.mifos.mobile.R import org.mifos.mobile.databinding.ActivitySavingsAccountApplicationBinding import org.mifos.mobile.ui.activities.base.BaseActivity import org.mifos.mobile.ui.enums.SavingsAccountState -import org.mifos.mobile.ui.fragments.SavingsAccountApplicationFragment.Companion.newInstance +import org.mifos.mobile.ui.savings_account_application.SavingsAccountApplicationFragment.Companion.newInstance /* * Created by saksham on 30/June/2018 diff --git a/app/src/main/java/org/mifos/mobile/ui/fragments/SavingsAccountApplicationFragment.kt b/app/src/main/java/org/mifos/mobile/ui/fragments/SavingsAccountApplicationFragment.kt deleted file mode 100644 index 0c23f6deb..000000000 --- a/app/src/main/java/org/mifos/mobile/ui/fragments/SavingsAccountApplicationFragment.kt +++ /dev/null @@ -1,238 +0,0 @@ -package org.mifos.mobile.ui.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import org.mifos.mobile.R -import org.mifos.mobile.api.local.PreferencesHelper -import org.mifos.mobile.databinding.FragmentSavingsAccountApplicationBinding -import org.mifos.mobile.models.accounts.savings.SavingsAccountApplicationPayload -import org.mifos.mobile.models.accounts.savings.SavingsAccountUpdatePayload -import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations -import org.mifos.mobile.models.templates.savings.ProductOptions -import org.mifos.mobile.models.templates.savings.SavingsAccountTemplate -import org.mifos.mobile.ui.activities.SavingsAccountContainerActivity -import org.mifos.mobile.ui.enums.SavingsAccountState -import org.mifos.mobile.ui.fragments.base.BaseFragment -import org.mifos.mobile.utils.Constants -import org.mifos.mobile.utils.DateHelper -import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedParcelable -import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedSerializable -import org.mifos.mobile.utils.SavingsAccountUiState -import org.mifos.mobile.utils.Toaster -import org.mifos.mobile.utils.getTodayFormatted -import org.mifos.mobile.viewModels.SavingsAccountApplicationViewModel -import javax.inject.Inject - -/* -* Created by saksham on 30/June/2018 -*/ -@AndroidEntryPoint -class SavingsAccountApplicationFragment : BaseFragment() { - - private var _binding: FragmentSavingsAccountApplicationBinding? = null - private val binding get() = _binding!! - private val viewModel: SavingsAccountApplicationViewModel by viewModels() - - - @JvmField - @Inject - var preferencesHelper: PreferencesHelper? = null - private var state: SavingsAccountState? = null - private var savingsWithAssociations: SavingsWithAssociations? = null - private var template: SavingsAccountTemplate? = null - private var productOptions: List? = null - private val productIdList: MutableList = ArrayList() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - (activity as? SavingsAccountContainerActivity)?.showToolbar() - if (arguments != null) { - state = requireArguments().getCheckedSerializable( - SavingsAccountState::class.java, - Constants.SAVINGS_ACCOUNT_STATE - ) as SavingsAccountState - savingsWithAssociations = arguments?.getCheckedParcelable( - SavingsWithAssociations::class.java, - Constants.SAVINGS_ACCOUNTS - ) - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - _binding = FragmentSavingsAccountApplicationBinding.inflate(inflater) - viewModel.loadSavingsAccountApplicationTemplate(preferencesHelper?.clientId, state) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.btnSubmit.setOnClickListener { - onSubmit() - } - - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.savingsAccountApplicationUiState.collect { state -> - when (state) { - SavingsAccountUiState.Loading -> showProgress() - - SavingsAccountUiState.HideProgress -> hideProgress() - - is SavingsAccountUiState.ShowUserInterfaceSavingAccountApplication -> { - hideProgress() - showUserInterfaceSavingAccountApplication(state.template) - } - - is SavingsAccountUiState.ShowUserInterfaceSavingAccountUpdate -> { - hideProgress() - showUserInterfaceSavingAccountUpdate(state.template) - } - - is SavingsAccountUiState.ErrorMessage -> { - hideProgress() - showError(state.error.message ?: "") - } - - SavingsAccountUiState.SavingsAccountApplicationSuccess -> { - hideProgress() - showSavingsAccountApplicationSuccessfully() - } - - SavingsAccountUiState.SavingsAccountUpdateSuccess -> { - hideProgress() - showSavingsAccountUpdateSuccessfully() - } - - is SavingsAccountUiState.Initial -> {} - - else -> throw IllegalStateException("Unexpected state : $state") - } - - } - } - } - } - - private fun showUserInterfaceSavingAccountApplication(template: SavingsAccountTemplate?) { - showUserInterface(template) - } - - private fun showSavingsAccountApplicationSuccessfully() { - showMessage(getString(R.string.new_saving_account_created_successfully)) - activity?.finish() - } - - private fun showUserInterfaceSavingAccountUpdate(template: SavingsAccountTemplate?) { - showUserInterface(template) - activity?.title = getString( - R.string.string_savings_account, - getString(R.string.update), - ) - binding.productIdField.setText(savingsWithAssociations?.savingsProductName!!, false) - } - - private fun showSavingsAccountUpdateSuccessfully() { - showMessage(getString(R.string.saving_account_updated_successfully)) - activity?.supportFragmentManager?.popBackStack() - } - - fun showUserInterface(template: SavingsAccountTemplate?) { - this.template = template - productOptions = template?.productOptions - if (productOptions != null) { - for ((_, name) in productOptions as ArrayList) { - productIdList.add(name) - } - } - binding.tvClientName.text = template?.clientName - binding.productIdField.setSimpleItems(productIdList.toTypedArray()) - binding.tvSubmissionDate.text = getTodayFormatted() - } - - private fun submitSavingsAccountApplication() { - val payload = SavingsAccountApplicationPayload() - payload.clientId = template?.clientId - - if (productIdList.indexOf(binding.productIdField.text.toString()) != -1) { - payload.productId = productIdList.indexOf(binding.productIdField.text.toString()) - .let { productOptions?.get(it)?.id } - } else { - Toaster.show(binding.root, getString(R.string.select_product_id)) - return - } - payload.submittedOnDate = DateHelper.getSpecificFormat( - DateHelper.FORMAT_dd_MMMM_yyyy, - getTodayFormatted(), - ) - viewModel.submitSavingsAccountApplication(payload) - } - - private fun updateSavingAccount() { - val payload = SavingsAccountUpdatePayload() - payload.clientId = template?.clientId?.toLong() - payload.productId = productIdList.indexOf(binding.productIdField.text.toString()) - .let { productOptions?.get(it)?.id }?.toLong() - viewModel.updateSavingsAccount(savingsWithAssociations?.id, payload) - } - - private fun onSubmit() { - if (state == SavingsAccountState.CREATE) { - submitSavingsAccountApplication() - } else { - updateSavingAccount() - } - } - - fun showError(error: String?) { - Toast.makeText(context, error, Toast.LENGTH_SHORT).show() - } - - private fun showMessage(showMessage: String?) { - Toast.makeText(context, showMessage, Toast.LENGTH_SHORT).show() - } - - fun showProgress() { - showMifosProgressDialog(getString(R.string.progress_message_loading)) - } - - fun hideProgress() { - hideMifosProgressDialog() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onDestroy() { - super.onDestroy() - hideProgress() - } - - companion object { - @JvmStatic - fun newInstance( - state: SavingsAccountState?, - savingsWithAssociations: SavingsWithAssociations?, - ): SavingsAccountApplicationFragment { - val fragment = SavingsAccountApplicationFragment() - val bundle = Bundle() - bundle.putSerializable(Constants.SAVINGS_ACCOUNT_STATE, state) - bundle.putParcelable(Constants.SAVINGS_ACCOUNTS, savingsWithAssociations) - fragment.arguments = bundle - return fragment - } - } -} diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account/SavingAccountsDetailFragment.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account/SavingAccountsDetailFragment.kt index dc32c83d7..c9faa4124 100644 --- a/app/src/main/java/org/mifos/mobile/ui/savings_account/SavingAccountsDetailFragment.kt +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account/SavingAccountsDetailFragment.kt @@ -23,7 +23,7 @@ import org.mifos.mobile.ui.enums.SavingsAccountState import org.mifos.mobile.ui.fragments.ClientChargeFragment import org.mifos.mobile.ui.fragments.QrCodeDisplayFragment import org.mifos.mobile.ui.fragments.SavingAccountsTransactionFragment -import org.mifos.mobile.ui.fragments.SavingsAccountApplicationFragment +import org.mifos.mobile.ui.savings_account_application.SavingsAccountApplicationFragment import org.mifos.mobile.ui.fragments.SavingsAccountWithdrawFragment import org.mifos.mobile.ui.fragments.SavingsMakeTransferFragment import org.mifos.mobile.ui.fragments.base.BaseFragment diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationContent.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationContent.kt new file mode 100644 index 000000000..e02843c62 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationContent.kt @@ -0,0 +1,183 @@ +package org.mifos.mobile.ui.savings_account_application + +import android.content.Context +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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 +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.R +import org.mifos.mobile.core.ui.theme.MifosMobileTheme +import org.mifos.mobile.models.templates.savings.SavingsAccountTemplate +import org.mifos.mobile.utils.getTodayFormatted + +@Composable +fun SavingsAccountApplicationContent( + 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)) { + + OutlinedCard( + colors = CardDefaults.outlinedCardColors( + containerColor = MaterialTheme.colorScheme.background, + ), + ) { + Column(modifier = Modifier.padding(20.dp)) { + TitleBodyRow( + titleText = stringResource(R.string.client_name), + bodyText = savingsAccountTemplate?.clientName ?: "" + ) + Spacer(modifier = Modifier.height(16.dp)) + TitleBodyRow( + titleText = stringResource(R.string.submission_date), + bodyText = getTodayFormatted() + ) + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + SelectProductIdDropDown( + existingProduct = existingProduct, + savingsAccountTemplate = savingsAccountTemplate, + selectProductId = { selectProductId = it } + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Button( + 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){ + Toast.makeText(context, context.getString(messageResId), Toast.LENGTH_LONG).show() +} + +@Composable +fun SelectProductIdDropDown( + existingProduct: String?, + 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 + ) { + OutlinedTextField( + value = selectedProduct, + onValueChange = { selectedProduct = it }, + label = { Text(stringResource(id = R.string.select_product_id)) }, + modifier = Modifier + .fillMaxWidth() + .clickable { expanded = !expanded }, + enabled = false, + readOnly = true, + colors = TextFieldDefaults.colors( + disabledTextColor = MaterialTheme.colorScheme.onSurface, + disabledContainerColor = MaterialTheme.colorScheme.background, + disabledLabelColor = MaterialTheme.colorScheme.onSurface + ), + trailingIcon = { + Icon( + imageVector = if (expanded) Icons.Filled.ArrowDropUp + else Icons.Filled.ArrowDropDown, + contentDescription = "Dropdown", + ) + } + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp) + ) { + productOptions.forEach { product -> + DropdownMenuItem( + onClick = { + selectProductId(product.id ?: -1) + selectedProduct = product.name ?: "" + expanded = false + }, + text = { Text(text = product.name ?: "") } + ) + } + } + } +} + +@Composable +fun TitleBodyRow(titleText: String, bodyText: String, modifier: Modifier = Modifier) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = titleText, + style = MaterialTheme.typography.labelMedium, + modifier = Modifier + .alpha(0.7f) + .weight(2f) + ) + Text( + text = bodyText, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(3f), + textAlign = TextAlign.Center + ) + } +} + + +@Preview(showSystemUi = true) +@Composable +fun SavingsAccountApplicationContentPreview() { + MifosMobileTheme { + SavingsAccountApplicationContent(null, null, {i, j, k -> }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationFragment.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationFragment.kt new file mode 100644 index 000000000..d644fbcdd --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationFragment.kt @@ -0,0 +1,75 @@ +package org.mifos.mobile.ui.savings_account_application + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.mifos.mobile.core.ui.theme.MifosMobileTheme +import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations +import org.mifos.mobile.ui.activities.base.BaseActivity +import org.mifos.mobile.ui.enums.SavingsAccountState +import org.mifos.mobile.ui.fragments.base.BaseFragment +import org.mifos.mobile.utils.Constants +import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedParcelable +import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedSerializable + +/* +* Created by saksham on 30/June/2018 +*/ +@AndroidEntryPoint +class SavingsAccountApplicationFragment : BaseFragment() { + + private val viewModel: SavingsAccountApplicationViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (activity as? BaseActivity)?.hideToolbar() + if (arguments != null) { + viewModel.setSavingsAccountState(arguments?.getCheckedSerializable(SavingsAccountState::class.java, Constants.SAVINGS_ACCOUNT_STATE) as SavingsAccountState) + viewModel.setSavingsWithAssociations(arguments?.getCheckedParcelable(SavingsWithAssociations::class.java, Constants.SAVINGS_ACCOUNTS)) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + viewModel.loadSavingsAccountApplicationTemplate() + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MifosMobileTheme { + SavingsAccountApplicationScreen( + uiState = viewModel.savingsAccountApplicationUiState.value, + savingsWithAssociations = viewModel.savingsWithAssociations, + navigateBack = { activity?.finish() }, + retryConnection = { viewModel.onRetry() }, + submit = { productId, clientId, showToast -> + viewModel.onSubmit(productId = productId, clientId = clientId, showToast = showToast) + } + ) + } + } + } + } + + companion object { + @JvmStatic + fun newInstance( + state: SavingsAccountState?, + savingsWithAssociations: SavingsWithAssociations?, + ): SavingsAccountApplicationFragment { + val fragment = SavingsAccountApplicationFragment() + val bundle = Bundle() + bundle.putSerializable(Constants.SAVINGS_ACCOUNT_STATE, state) + bundle.putParcelable(Constants.SAVINGS_ACCOUNTS, savingsWithAssociations) + fragment.arguments = bundle + return fragment + } + } +} diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationScreen.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationScreen.kt new file mode 100644 index 000000000..2a0301a39 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationScreen.kt @@ -0,0 +1,109 @@ +package org.mifos.mobile.ui.savings_account_application + +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +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 org.mifos.mobile.R +import org.mifos.mobile.core.ui.component.EmptyDataView +import org.mifos.mobile.core.ui.component.MifosProgressIndicator +import org.mifos.mobile.core.ui.component.MifosTopBar +import org.mifos.mobile.core.ui.component.NoInternet +import org.mifos.mobile.core.ui.theme.MifosMobileTheme +import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations +import org.mifos.mobile.ui.enums.SavingsAccountState +import org.mifos.mobile.utils.Network + + +@Composable +fun SavingsAccountApplicationScreen( + uiState: SavingsAccountApplicationUiState? = null, + savingsWithAssociations: SavingsWithAssociations? = null, + navigateBack: () -> Unit, + submit: (Int, Int, showToast: (Int) -> Unit) -> Unit, + retryConnection: () -> Unit, +) { + var topBarTitleText by rememberSaveable { mutableStateOf("") } + val context = LocalContext.current + + Column { + MifosTopBar( + navigateBack = navigateBack, + title = { Text(text = topBarTitleText) } + ) + + when (uiState) { + is SavingsAccountApplicationUiState.Error -> { + ErrorComponent(retryConnection = retryConnection, errorMessage = uiState.errorMessage) + } + + is SavingsAccountApplicationUiState.Loading -> { + MifosProgressIndicator(modifier = Modifier.fillMaxSize()) + } + + is SavingsAccountApplicationUiState.ShowUserInterface -> { + val (titleResourceId, existingProduct) = when (uiState.requestType) { + SavingsAccountState.CREATE -> R.string.apply_savings_account to null + else -> R.string.update_savings_account to savingsWithAssociations?.savingsProductName + } + + topBarTitleText = stringResource(id = titleResourceId) + SavingsAccountApplicationContent( + existingProduct = existingProduct, + savingsAccountTemplate = uiState.template, + submit = submit + ) + } + + is SavingsAccountApplicationUiState.Success -> { + val messageResourceId = when (uiState.requestType) { + 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() + navigateBack.invoke() + } + + else -> {} + } + } +} + +@Composable +fun ErrorComponent(retryConnection: () -> Unit, errorMessage: String?) { + val context = LocalContext.current + if (!Network.isConnected(context)) { + NoInternet( + icon = R.drawable.ic_portable_wifi_off_black_24dp, + error = R.string.no_internet_connection, + isRetryEnabled = true, + retry = retryConnection + ) + Toast.makeText(context, stringResource(R.string.internet_not_connected), Toast.LENGTH_SHORT,).show() + } else { + EmptyDataView( + modifier= Modifier.fillMaxSize(), + icon = R.drawable.ic_error_black_24dp, + error = R.string.error_saving_account_details_loading, + errorString = errorMessage + ) + } +} + +@Preview(showSystemUi = true) +@Composable +fun SavingsAccountApplicationScreenPreview() { + MifosMobileTheme { + SavingsAccountApplicationScreen(null, null, {}, { i, j, k -> }, {}) + } +} + diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationViewModel.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationViewModel.kt new file mode 100644 index 000000000..1dc27a755 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_application/SavingsAccountApplicationViewModel.kt @@ -0,0 +1,127 @@ +package org.mifos.mobile.ui.savings_account_application + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import org.mifos.mobile.R +import org.mifos.mobile.api.local.PreferencesHelper +import org.mifos.mobile.models.accounts.savings.SavingsAccountApplicationPayload +import org.mifos.mobile.models.accounts.savings.SavingsAccountUpdatePayload +import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations +import org.mifos.mobile.models.templates.savings.SavingsAccountTemplate +import org.mifos.mobile.repositories.SavingsAccountRepository +import org.mifos.mobile.ui.enums.SavingsAccountState +import org.mifos.mobile.utils.DateHelper +import org.mifos.mobile.utils.getTodayFormatted +import javax.inject.Inject + +@HiltViewModel +class SavingsAccountApplicationViewModel @Inject constructor( + private val savingsAccountRepositoryImp: SavingsAccountRepository, + private val preferencesHelper: PreferencesHelper +) : ViewModel() { + + val savingsAccountApplicationUiState: State get() = _savingsAccountApplicationUiState + private val _savingsAccountApplicationUiState = + mutableStateOf(SavingsAccountApplicationUiState.Loading) + + private val savingsAccountState get() = _savingsAccountState + private var _savingsAccountState: SavingsAccountState = SavingsAccountState.CREATE + + val savingsWithAssociations get() = _savingsWithAssociations + private var _savingsWithAssociations: SavingsWithAssociations? = null + + private val clientId get() = preferencesHelper.clientId + + fun loadSavingsAccountApplicationTemplate() { + viewModelScope.launch { + _savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading + savingsAccountRepositoryImp.getSavingAccountApplicationTemplate(clientId).catch { e -> + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.Error(e.message) + }.collect { + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.ShowUserInterface(it, savingsAccountState) + } + } + } + + fun submitSavingsAccountApplication(payload: SavingsAccountApplicationPayload?) { + viewModelScope.launch { + _savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading + savingsAccountRepositoryImp.submitSavingAccountApplication(payload).catch { e -> + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.Error(e.message) + }.collect { + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.Success(savingsAccountState) + } + } + } + + fun updateSavingsAccount(accountId: Long?, payload: SavingsAccountUpdatePayload?) { + viewModelScope.launch { + _savingsAccountApplicationUiState.value = SavingsAccountApplicationUiState.Loading + savingsAccountRepositoryImp.updateSavingsAccount(accountId, payload).catch { e -> + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.Error(e.message) + }.collect { + _savingsAccountApplicationUiState.value = + SavingsAccountApplicationUiState.Success(savingsAccountState) + } + } + } + + fun setSavingsAccountState(savingsAccountState: SavingsAccountState) { + _savingsAccountState = savingsAccountState + } + + fun setSavingsWithAssociations(savingsAssociations: SavingsWithAssociations?) { + _savingsWithAssociations = savingsAssociations + } + + fun onRetry() { + loadSavingsAccountApplicationTemplate() + } + + fun onSubmit(productId: Int, clientId: Int, showToast: (Int) -> Unit) { + if (savingsAccountState == SavingsAccountState.CREATE) { + submitSavingsAccount(productId = productId, clientId = clientId, showToast = showToast) + } else { + updateSavingAccount(productId = productId, clientId = clientId) + } + } + + private fun updateSavingAccount(productId: Int, clientId: Int) { + val payload = SavingsAccountUpdatePayload() + payload.clientId = clientId.toLong() + payload.productId = productId.toLong() + updateSavingsAccount(savingsWithAssociations?.id, payload) + } + + private fun submitSavingsAccount(productId: Int, clientId: Int, showToast: (Int) -> Unit) { + val payload = SavingsAccountApplicationPayload() + payload.clientId = clientId + if (productId != -1) { + payload.productId = productId + } else { + showToast(R.string.select_product_id) + return + } + payload.submittedOnDate = DateHelper.getSpecificFormat(DateHelper.FORMAT_dd_MMMM_yyyy, getTodayFormatted(),) + submitSavingsAccountApplication(payload) + } +} + +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) : + SavingsAccountApplicationUiState() +} + diff --git a/app/src/main/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModel.kt b/app/src/main/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModel.kt deleted file mode 100644 index 261bd74fb..000000000 --- a/app/src/main/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModel.kt +++ /dev/null @@ -1,74 +0,0 @@ -package org.mifos.mobile.viewModels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.launch -import org.mifos.mobile.models.accounts.savings.SavingsAccountApplicationPayload -import org.mifos.mobile.models.accounts.savings.SavingsAccountUpdatePayload -import org.mifos.mobile.repositories.SavingsAccountRepository -import org.mifos.mobile.ui.enums.SavingsAccountState -import org.mifos.mobile.utils.SavingsAccountUiState -import javax.inject.Inject - -@HiltViewModel -class SavingsAccountApplicationViewModel @Inject constructor(private val savingsAccountRepositoryImp: SavingsAccountRepository) : - ViewModel() { - - private val _savingsAccountApplicationUiState = - MutableStateFlow(SavingsAccountUiState.Initial) - val savingsAccountApplicationUiState: StateFlow get() = _savingsAccountApplicationUiState - - fun loadSavingsAccountApplicationTemplate( - clientId: Long?, - state: SavingsAccountState?, - ) { - viewModelScope.launch { - _savingsAccountApplicationUiState.value = SavingsAccountUiState.Loading - savingsAccountRepositoryImp.getSavingAccountApplicationTemplate(clientId).catch { e -> - _savingsAccountApplicationUiState.value = SavingsAccountUiState.ErrorMessage(e) - }.collect { - if (state === SavingsAccountState.CREATE) { - _savingsAccountApplicationUiState.value = - SavingsAccountUiState.ShowUserInterfaceSavingAccountApplication(it) - - } else { - _savingsAccountApplicationUiState.value = - SavingsAccountUiState.ShowUserInterfaceSavingAccountUpdate(it) - } - } - } - } - - fun submitSavingsAccountApplication(payload: SavingsAccountApplicationPayload?) { - viewModelScope.launch { - _savingsAccountApplicationUiState.value = SavingsAccountUiState.Loading - savingsAccountRepositoryImp.submitSavingAccountApplication(payload).catch { e -> - _savingsAccountApplicationUiState.value = SavingsAccountUiState.ErrorMessage(e) - }.onCompletion { - _savingsAccountApplicationUiState.value = - SavingsAccountUiState.SavingsAccountApplicationSuccess - }.collect { - _savingsAccountApplicationUiState.value = SavingsAccountUiState.HideProgress - } - } - } - - fun updateSavingsAccount(accountId: Long?, payload: SavingsAccountUpdatePayload?) { - viewModelScope.launch { - _savingsAccountApplicationUiState.value = SavingsAccountUiState.Loading - savingsAccountRepositoryImp.updateSavingsAccount(accountId, payload).catch { e -> - _savingsAccountApplicationUiState.value = SavingsAccountUiState.ErrorMessage(e) - }.onCompletion { - _savingsAccountApplicationUiState.value = - SavingsAccountUiState.SavingsAccountUpdateSuccess - }.collect { - _savingsAccountApplicationUiState.value = SavingsAccountUiState.HideProgress - } - } - } -} \ No newline at end of file diff --git a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt index 280c3c2de..440bb2d17 100644 --- a/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt +++ b/app/src/test/java/org/mifos/mobile/viewModels/SavingsAccountApplicationViewModelTest.kt @@ -16,6 +16,7 @@ import org.mifos.mobile.models.accounts.savings.SavingsAccountUpdatePayload import org.mifos.mobile.models.templates.savings.SavingsAccountTemplate import org.mifos.mobile.repositories.SavingsAccountRepository import org.mifos.mobile.ui.enums.SavingsAccountState +import org.mifos.mobile.ui.savings_account_application.SavingsAccountApplicationViewModel import org.mifos.mobile.util.RxSchedulersOverrideRule import org.mifos.mobile.utils.SavingsAccountUiState import org.mockito.Mock diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt index d8a81b54b..a6c7aee45 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/EmptyDataView.kt @@ -21,7 +21,8 @@ import androidx.compose.ui.unit.sp fun EmptyDataView( modifier: Modifier = Modifier, icon: Int, - error: Int + error: Int, + errorString: String? = null, ) { Column( modifier = modifier, @@ -38,7 +39,8 @@ fun EmptyDataView( ) Text( - text = stringResource(id = error), + modifier= Modifier.padding(horizontal = 20.dp), + text = errorString ?: stringResource(id = error), style = TextStyle(fontSize = 20.sp), color = MaterialTheme.colorScheme.onSecondary, textAlign = TextAlign.Center