diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt index 1edb8f0990a..f54dc368256 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt @@ -61,7 +61,7 @@ fun MifosScaffold( Text( text = it, style = TextStyle( - fontSize = 22.sp, + fontSize = 12.sp, fontWeight = FontWeight.Medium, fontStyle = FontStyle.Normal ), diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionFragment.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionFragment.kt index 5192f77bcc2..e67376abd76 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionFragment.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionFragment.kt @@ -1,282 +1,32 @@ package com.mifos.mifosxdroid.offline.syncloanrepaymenttransacition -import android.content.DialogInterface import android.os.Bundle -import android.util.Log import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import com.mifos.core.objects.accounts.loan.LoanRepaymentRequest -import com.mifos.mifosxdroid.R -import com.mifos.mifosxdroid.adapters.SyncLoanRepaymentAdapter -import com.mifos.mifosxdroid.core.MaterialDialog +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.navigation.fragment.findNavController import com.mifos.mifosxdroid.core.MifosBaseFragment -import com.mifos.mifosxdroid.core.util.Toaster.show -import com.mifos.mifosxdroid.databinding.FragmentSyncpayloadBinding -import com.mifos.utils.Constants -import com.mifos.utils.PrefManager.userStatus import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject /** * Created by Rajan Maurya on 28/07/16. */ @AndroidEntryPoint -class SyncLoanRepaymentTransactionFragment : MifosBaseFragment(), DialogInterface.OnClickListener { - - private lateinit var binding: FragmentSyncpayloadBinding - - val LOG_TAG = javaClass.simpleName - - private lateinit var viewModel: SyncLoanRepaymentTransactionViewModel - - @JvmField - @Inject - var mSyncLoanRepaymentAdapter: SyncLoanRepaymentAdapter? = null - private var mLoanRepaymentRequests: MutableList? = null - private var mClientSyncIndex = 0 - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - mLoanRepaymentRequests = ArrayList() - setHasOptionsMenu(true) - } +class SyncLoanRepaymentTransactionFragment : MifosBaseFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentSyncpayloadBinding.inflate(inflater, container, false) - viewModel = ViewModelProvider(this)[SyncLoanRepaymentTransactionViewModel::class.java] - val mLayoutManager = LinearLayoutManager(activity) - mLayoutManager.orientation = LinearLayoutManager.VERTICAL - binding.rvSyncPayload.layoutManager = mLayoutManager - binding.rvSyncPayload.setHasFixedSize(true) - binding.rvSyncPayload.adapter = mSyncLoanRepaymentAdapter - /** - * Loading All Loan Repayment Offline Transaction from Database - */ - binding.swipeContainer.setColorSchemeColors( - *requireActivity() - .resources.getIntArray(R.array.swipeRefreshColors) - ) - binding.swipeContainer.setOnRefreshListener { //Loading LoanRepayment Transactions and PaymentTypeOptions From Database - viewModel.loadDatabaseLoanRepaymentTransactions() - viewModel.loanPaymentTypeOption() - if (binding.swipeContainer.isRefreshing) binding.swipeContainer.isRefreshing = false - } - - //Loading LoanRepayment Transactions and PaymentTypeOptions From Database - viewModel.loadDatabaseLoanRepaymentTransactions() - viewModel.loanPaymentTypeOption() - - viewModel.syncLoanRepaymentTransactionUiState.observe(viewLifecycleOwner) { - when (it) { - is SyncLoanRepaymentTransactionUiState.ShowError -> { - showProgressbar(false) - showError(it.message) - } - - is SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentDeletedAndUpdateLoanRepayment -> { - showProgressbar(false) - showLoanRepaymentDeletedAndUpdateLoanRepayment(it.loanRepaymentRequests) - } - - is SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentTransactions -> { - showProgressbar(false) - showLoanRepaymentTransactions(it.loanRepaymentRequests) - } - - is SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentUpdated -> { - showProgressbar(false) - showLoanRepaymentUpdated(it.loanRepaymentRequest) - } - - is SyncLoanRepaymentTransactionUiState.ShowPaymentFailed -> { - showProgressbar(false) - showPaymentFailed(it.message) - } - - is SyncLoanRepaymentTransactionUiState.ShowPaymentSubmittedSuccessfully -> { - showProgressbar(false) - showPaymentSubmittedSuccessfully() - } - - is SyncLoanRepaymentTransactionUiState.ShowPaymentTypeOption -> { - showProgressbar(false) - showPaymentTypeOption(it.paymentTypeOptions) - } - - is SyncLoanRepaymentTransactionUiState.ShowProgressbar -> showProgressbar(true) - } - } - - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.noPayloadIcon.setOnClickListener { - reloadOnError() - } - } - - /** - * Show when Database response is null or failed to fetch the List - * Onclick Send Fresh Request for List. - */ - - private fun reloadOnError() { - binding.llError.visibility = View.GONE - viewModel.loadDatabaseLoanRepaymentTransactions() - } - - private fun showOfflineModeDialog() { - MaterialDialog.Builder().init(activity) - .setTitle(R.string.offline_mode) - .setMessage(R.string.dialog_message_offline_sync_alert) - .setPositiveButton(R.string.dialog_action_go_online, this) - .setNegativeButton(R.string.dialog_action_cancel, this) - .createMaterialDialog() - .show() - } - - override fun onClick(dialog: DialogInterface, which: Int) { - when (which) { - DialogInterface.BUTTON_NEGATIVE -> {} - DialogInterface.BUTTON_POSITIVE -> { - userStatus = Constants.USER_ONLINE - if (mLoanRepaymentRequests!!.isNotEmpty()) { - mClientSyncIndex = 0 - syncGroupPayload() - } else { - show( - binding.root, - requireActivity().resources.getString(R.string.nothing_to_sync) - ) - } - } - - else -> {} - } - } - - private fun showLoanRepaymentTransactions(loanRepaymentRequests: List) { - mLoanRepaymentRequests = loanRepaymentRequests as MutableList - if (loanRepaymentRequests.isEmpty()) { - binding.llError.visibility = View.VISIBLE - binding.noPayloadText.text = requireActivity() - .resources.getString(R.string.no_repayment_to_sync) - binding.noPayloadIcon.setImageResource(R.drawable.ic_assignment_turned_in_black_24dp) - } else { - mSyncLoanRepaymentAdapter!!.setLoanRepaymentRequests(loanRepaymentRequests) - } - } - - private fun showPaymentTypeOption(paymentTypeOptions: List) { - mSyncLoanRepaymentAdapter!!.setPaymentTypeOptions(paymentTypeOptions) - } - - private fun showPaymentSubmittedSuccessfully() { - mLoanRepaymentRequests?.get(mClientSyncIndex)?.loanId?.let { - viewModel - .deleteAndUpdateLoanRepayments( - it - ) - } - } - - private fun showPaymentFailed(errorMessage: String) { - val loanRepaymentRequest = mLoanRepaymentRequests!![mClientSyncIndex] - loanRepaymentRequest.errorMessage = errorMessage - viewModel.updateLoanRepayment(loanRepaymentRequest) - } - - private fun showLoanRepaymentUpdated(loanRepaymentRequest: LoanRepaymentRequest) { - mLoanRepaymentRequests?.set(mClientSyncIndex, loanRepaymentRequest) - mSyncLoanRepaymentAdapter!!.notifyDataSetChanged() - mClientSyncIndex += 1 - if (mLoanRepaymentRequests!!.size != mClientSyncIndex) { - syncGroupPayload() - } - } - - private fun showLoanRepaymentDeletedAndUpdateLoanRepayment(loanRepaymentRequests: List) { - mClientSyncIndex = 0 - mLoanRepaymentRequests = loanRepaymentRequests as MutableList - mSyncLoanRepaymentAdapter!!.setLoanRepaymentRequests(loanRepaymentRequests) - if (mLoanRepaymentRequests!!.isNotEmpty()) { - syncGroupPayload() - } else { - binding.llError.visibility = View.VISIBLE - binding.noPayloadText.text = requireActivity() - .resources.getString(R.string.all_repayment_synced) - binding.noPayloadIcon.setImageResource(R.drawable.ic_assignment_turned_in_black_24dp) - } - } - - private fun showError(stringId: Int) { - binding.llError.visibility = View.VISIBLE - val message = - stringId.toString() + requireActivity().resources.getString(R.string.click_to_refresh) - binding.noPayloadText.text = message - show(binding.root, stringId) - } - - private fun showProgressbar(show: Boolean) { - binding.swipeContainer.isRefreshing = show - if (show && mSyncLoanRepaymentAdapter!!.itemCount == 0) { - showMifosProgressBar() - binding.swipeContainer.isRefreshing = false - } else { - hideMifosProgressBar() - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_sync, menu) - super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.action_sync) { - when (userStatus) { - false -> if (mLoanRepaymentRequests!!.isNotEmpty()) { - mClientSyncIndex = 0 - syncGroupPayload() - } else { - show( - binding.root, - requireActivity().resources.getString(R.string.nothing_to_sync) - ) - } - - true -> showOfflineModeDialog() - } - } - return super.onOptionsItemSelected(item) - } - - private fun syncGroupPayload() { - for (i in mLoanRepaymentRequests!!.indices) { - if (mLoanRepaymentRequests?.get(i)?.errorMessage == null) { - mLoanRepaymentRequests?.get(i)?.loanId?.let { - viewModel.syncLoanRepayment( - it, mLoanRepaymentRequests?.get(i) - ) - } - mClientSyncIndex = i - break - } else { - Log.d( - LOG_TAG, - requireActivity().resources.getString(R.string.error_fix_before_sync) + - mLoanRepaymentRequests!![i].errorMessage + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + SyncLoanRepaymentTransactionScreenRoute( + onBackPressed = { + findNavController().popBackStack() + } ) } } diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionScreen.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionScreen.kt new file mode 100644 index 00000000000..9543033c57a --- /dev/null +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionScreen.kt @@ -0,0 +1,344 @@ +package com.mifos.mifosxdroid.offline.syncloanrepaymenttransacition + +import android.content.Context +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AssignmentTurnedIn +import androidx.compose.material.icons.filled.Error +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +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 +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.mifos.core.designsystem.component.MifosCircularProgress +import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.icon.MifosIcons +import com.mifos.core.objects.PaymentTypeOption +import com.mifos.core.objects.accounts.loan.LoanRepaymentRequest +import com.mifos.mifosxdroid.R +import com.mifos.utils.Network +import com.mifos.utils.PrefManager.userStatus +import com.mifos.utils.Utils.getPaymentTypeName + +@Composable +fun SyncLoanRepaymentTransactionScreenRoute( + viewModel: SyncLoanRepaymentTransactionViewModel = hiltViewModel(), + onBackPressed: () -> Unit, +) { + val uiState by viewModel.syncLoanRepaymentTransactionUiState.collectAsStateWithLifecycle() + val refreshState by viewModel.isRefreshing.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = Unit) { + viewModel.loadDatabaseLoanRepaymentTransactions() + viewModel.loanPaymentTypeOption() + } + + SyncLoanRepaymentTransactionScreen( + uiState = uiState, + onBackPressed = onBackPressed, + refreshState = refreshState, + onRefresh = { + viewModel.refreshTransactions() + }, + syncLoanRepaymentTransactions = { + viewModel.syncGroupPayload() + } + ) +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SyncLoanRepaymentTransactionScreen( + uiState: SyncLoanRepaymentTransactionUiState, + onBackPressed: () -> Unit, + refreshState: Boolean, + onRefresh: () -> Unit, + syncLoanRepaymentTransactions: () -> Unit, +) { + val snackbarHostState = remember { SnackbarHostState() } + val context = LocalContext.current + val pullRefreshState = + rememberPullRefreshState(refreshing = refreshState, onRefresh = onRefresh) + + MifosScaffold( + icon = MifosIcons.arrowBack, + title = stringResource(id = R.string.sync_loanrepayment), + onBackPressed = onBackPressed, + actions = { + IconButton(onClick = { + when (userStatus) { + false -> checkNetworkConnectionAndSync(context, syncLoanRepaymentTransactions) + true -> TODO("Implement OfflineModeDialog()") + } + }) { + Icon( + MifosIcons.sync, + contentDescription = stringResource(id = R.string.sync_loanrepayment) + ) + } + }, + snackbarHostState = snackbarHostState + ) { paddingValues -> + Column(modifier = Modifier.padding(paddingValues)) { + Box(modifier = Modifier.pullRefresh(pullRefreshState)) { + when (uiState) { + is SyncLoanRepaymentTransactionUiState.ShowProgressbar -> { + MifosCircularProgress() + } + + is SyncLoanRepaymentTransactionUiState.ShowError -> { + ErrorStateScreen(uiState.message.toString(), onRefresh) + } + + is SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentTransactions -> { + LoanRepaymentTransactionsList( + uiState.loanRepaymentRequests, + uiState.paymentTypeOptions + ) + } + + is SyncLoanRepaymentTransactionUiState.ShowEmptyLoanRepayments -> { + EmptyLoanRepaymentsScreen(uiState.message) + } + } + PullRefreshIndicator( + refreshing = refreshState, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + } + } + } +} + +@Composable +fun LoanRepaymentTransactionsList( + loanRepaymentRequests: List, + paymentTypeOptions: List +) { + LazyColumn { + items(loanRepaymentRequests) { request -> + LoanRepaymentTransactionItem(request, paymentTypeOptions) + } + } +} + +@Composable +fun LoanRepaymentTransactionItem( + request: LoanRepaymentRequest, + paymentTypeOptions: List +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(2.dp), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + shape = RoundedCornerShape(2.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + TransactionRow(stringResource(R.string.loan_id), request.loanId.toString()) + TransactionRow(stringResource(R.string.account_number), request.accountNumber ?: "") + TransactionRow( + stringResource(R.string.payment_type), + request.paymentTypeId?.let { getPaymentTypeName(it.toInt(), paymentTypeOptions) } + ?: "" + ) + TransactionRow( + stringResource(R.string.transaction_amount), + request.transactionAmount ?: "" + ) + TransactionRow( + stringResource(R.string.loan_transaction_date), + request.transactionDate ?: "" + ) + + if (request.errorMessage != null) { + Text( + text = request.errorMessage!!, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } +} + +@Composable +fun TransactionRow(label: String, value: String) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + ) { + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + Text( + text = value, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.weight(1f) + ) + } +} + +@Composable +fun ErrorStateScreen(message: String, onRefresh: () -> Unit) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Default.Error, + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + Text(text = message, modifier = Modifier.padding(vertical = 6.dp)) + Button(onClick = onRefresh) { + Text(stringResource(id = R.string.click_to_refresh)) + } + } +} + +@Composable +fun EmptyLoanRepaymentsScreen(message: String) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Default.AssignmentTurnedIn, + contentDescription = null, + modifier = Modifier.size(72.dp), + tint = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = message, + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurface + ) + } +} + +fun checkNetworkConnectionAndSync( + context: Context, + syncLoanRepaymentTransactions: () -> Unit +) { + if (Network.isOnline(context)) { + syncLoanRepaymentTransactions() + } else { + Toast.makeText( + context, + context.getString(R.string.error_not_connected_internet), + Toast.LENGTH_SHORT + ).show() + } +} + +class SyncLoanRepaymentTransactionUiStateProvider : + PreviewParameterProvider { + override val values = sequenceOf( + SyncLoanRepaymentTransactionUiState.ShowProgressbar, + SyncLoanRepaymentTransactionUiState.ShowError(R.string.failed_to_load_loanrepayment), + SyncLoanRepaymentTransactionUiState.ShowEmptyLoanRepayments("No loan repayments to sync"), + SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentTransactions( + sampleLoanRepaymentRequests, + samplePaymentTypeOptions + ) + ) +} + +@Preview(showBackground = true) +@Composable +private fun SyncLoanRepaymentTransactionScreenPreview( + @PreviewParameter(SyncLoanRepaymentTransactionUiStateProvider::class) uiState: SyncLoanRepaymentTransactionUiState +) { + SyncLoanRepaymentTransactionScreen( + uiState = uiState, + onBackPressed = {}, + refreshState = false, + onRefresh = {}, + syncLoanRepaymentTransactions = {} + ) +} + +// Sample data for previews +val sampleLoanRepaymentRequests = List(5) { index -> + LoanRepaymentRequest( + loanId = index, + accountNumber = "LOAN-$index", + paymentTypeId = index.toString(), + transactionAmount = "${1000 + index * 100}", + transactionDate = "2023-07-${15 + index}", + errorMessage = if (index % 2 == 0) null else "Error in transaction" + ) +} + +val samplePaymentTypeOptions = List(3) { index -> + PaymentTypeOption( + id = index, + name = "Payment Type $index", + description = "Description for Payment Type $index", + isCashPayment = index % 2 == 0, + position = index + ) +} + +// Individual preview for LoanRepaymentTransactionItem +@Preview(showBackground = true) +@Composable +fun LoanRepaymentTransactionItemPreview() { + LoanRepaymentTransactionItem( + request = sampleLoanRepaymentRequests[0], + paymentTypeOptions = samplePaymentTypeOptions + ) +} \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionUiState.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionUiState.kt index e45a7826c3a..7c87042e87c 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionUiState.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionUiState.kt @@ -1,5 +1,6 @@ package com.mifos.mifosxdroid.offline.syncloanrepaymenttransacition +import com.mifos.core.objects.PaymentTypeOption import com.mifos.core.objects.accounts.loan.LoanRepaymentRequest /** @@ -11,19 +12,10 @@ sealed class SyncLoanRepaymentTransactionUiState { data class ShowError(val message: Int) : SyncLoanRepaymentTransactionUiState() - data class ShowLoanRepaymentTransactions(val loanRepaymentRequests: List) : - SyncLoanRepaymentTransactionUiState() + data class ShowLoanRepaymentTransactions( + val loanRepaymentRequests: List, + val paymentTypeOptions: List + ) : SyncLoanRepaymentTransactionUiState() - data class ShowPaymentTypeOption(val paymentTypeOptions: List) : - SyncLoanRepaymentTransactionUiState() - - data class ShowPaymentFailed(val message: String) : SyncLoanRepaymentTransactionUiState() - - object ShowPaymentSubmittedSuccessfully : SyncLoanRepaymentTransactionUiState() - - data class ShowLoanRepaymentDeletedAndUpdateLoanRepayment(val loanRepaymentRequests: List) : - SyncLoanRepaymentTransactionUiState() - - data class ShowLoanRepaymentUpdated(val loanRepaymentRequest: LoanRepaymentRequest) : - SyncLoanRepaymentTransactionUiState() + data class ShowEmptyLoanRepayments(val message: String) : SyncLoanRepaymentTransactionUiState() } \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionViewModel.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionViewModel.kt index 172a01a9e34..fddc695abd0 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionViewModel.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/offline/syncloanrepaymenttransacition/SyncLoanRepaymentTransactionViewModel.kt @@ -1,12 +1,17 @@ package com.mifos.mifosxdroid.offline.syncloanrepaymenttransacition -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import android.util.Log import androidx.lifecycle.ViewModel +import com.mifos.core.data.CenterPayload_Table.errorMessage +import com.mifos.core.objects.PaymentTypeOption import com.mifos.core.objects.accounts.loan.LoanRepaymentRequest import com.mifos.core.objects.accounts.loan.LoanRepaymentResponse import com.mifos.mifosxdroid.R +import com.mifos.mifosxdroid.dialogfragments.syncclientsdialog.SyncClientsDialogFragment.Companion.LOG_TAG import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import rx.Subscriber import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers @@ -20,10 +25,25 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo ViewModel() { private val _syncLoanRepaymentTransactionUiState = - MutableLiveData() - - val syncLoanRepaymentTransactionUiState: LiveData - get() = _syncLoanRepaymentTransactionUiState + MutableStateFlow( + SyncLoanRepaymentTransactionUiState.ShowProgressbar + ) + val syncLoanRepaymentTransactionUiState: StateFlow = + _syncLoanRepaymentTransactionUiState + + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing: StateFlow = _isRefreshing.asStateFlow() + + private var mLoanRepaymentRequests: MutableList = mutableListOf() + private var mPaymentTypeOptions: List = emptyList() + private var mClientSyncIndex = 0 + + fun refreshTransactions() { + _isRefreshing.value = true + loadDatabaseLoanRepaymentTransactions() + loanPaymentTypeOption() + _isRefreshing.value = false + } fun loadDatabaseLoanRepaymentTransactions() { _syncLoanRepaymentTransactionUiState.value = @@ -39,10 +59,8 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo } override fun onNext(loanRepaymentRequests: List) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentTransactions( - loanRepaymentRequests - ) + mLoanRepaymentRequests = loanRepaymentRequests.toMutableList() + updateUiState() } }) @@ -55,22 +73,36 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo .paymentTypeOption() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber>() { + .subscribe(object : Subscriber>() { override fun onCompleted() {} override fun onError(e: Throwable) { _syncLoanRepaymentTransactionUiState.value = SyncLoanRepaymentTransactionUiState.ShowError(R.string.failed_to_load_paymentoptions) } - override fun onNext(paymentTypeOptions: List) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowPaymentTypeOption(paymentTypeOptions) + override fun onNext(paymentTypeOptions: List) { + mPaymentTypeOptions = paymentTypeOptions + updateUiState() } }) + } + private fun updateUiState() { + if (mLoanRepaymentRequests.isNotEmpty()) { + _syncLoanRepaymentTransactionUiState.value = + SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentTransactions( + mLoanRepaymentRequests, + mPaymentTypeOptions + ) + } else { + _syncLoanRepaymentTransactionUiState.value = + SyncLoanRepaymentTransactionUiState.ShowEmptyLoanRepayments( + R.string.no_loanrepayment_to_sync.toString() + ) + } } - fun syncLoanRepayment(loanId: Int, loanRepaymentRequest: LoanRepaymentRequest?) { + private fun syncLoanRepayment(loanId: Int, loanRepaymentRequest: LoanRepaymentRequest?) { _syncLoanRepaymentTransactionUiState.value = SyncLoanRepaymentTransactionUiState.ShowProgressbar repository.submitPayment(loanId, loanRepaymentRequest!!) @@ -79,13 +111,17 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo .subscribe(object : Subscriber() { override fun onCompleted() {} override fun onError(e: Throwable) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowPaymentFailed(e.message.toString()) + val eLoanRepaymentRequest = mLoanRepaymentRequests[mClientSyncIndex] + eLoanRepaymentRequest.errorMessage = errorMessage.toString() + updateLoanRepayment(eLoanRepaymentRequest) } override fun onNext(loanRepaymentResponse: LoanRepaymentResponse) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowPaymentSubmittedSuccessfully + mLoanRepaymentRequests[mClientSyncIndex].loanId?.let { + deleteAndUpdateLoanRepayments( + it + ) + } } }) @@ -105,10 +141,18 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo } override fun onNext(loanRepaymentRequests: List) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentDeletedAndUpdateLoanRepayment( - loanRepaymentRequests - ) + mClientSyncIndex = 0 + mLoanRepaymentRequests = + loanRepaymentRequests as MutableList + if (mLoanRepaymentRequests.isNotEmpty()) { + syncGroupPayload() + } else { + _syncLoanRepaymentTransactionUiState.value = + SyncLoanRepaymentTransactionUiState.ShowEmptyLoanRepayments( + R.string.no_loanrepayment_to_sync.toString() + ) + } + updateUiState() } }) @@ -128,12 +172,34 @@ class SyncLoanRepaymentTransactionViewModel @Inject constructor(private val repo } override fun onNext(loanRepaymentRequest: LoanRepaymentRequest) { - _syncLoanRepaymentTransactionUiState.value = - SyncLoanRepaymentTransactionUiState.ShowLoanRepaymentUpdated( - loanRepaymentRequest - ) + mLoanRepaymentRequests[mClientSyncIndex] = loanRepaymentRequest + mClientSyncIndex += 1 + if (mLoanRepaymentRequests.size != mClientSyncIndex) { + syncGroupPayload() + } + updateUiState() } }) + } + fun syncGroupPayload() { + for (i in mLoanRepaymentRequests.indices) { + if (mLoanRepaymentRequests[i].errorMessage == null) { + mLoanRepaymentRequests[i].loanId?.let { + syncLoanRepayment( + it, mLoanRepaymentRequests[i] + ) + } + mClientSyncIndex = i + break + } else { + mLoanRepaymentRequests[i].errorMessage?.let { + Log.d( + LOG_TAG, + it + ) + } + } + } } } \ No newline at end of file diff --git a/mifosng-android/src/main/res/values/strings.xml b/mifosng-android/src/main/res/values/strings.xml index b0891f66055..65f6a6a8c5a 100755 --- a/mifosng-android/src/main/res/values/strings.xml +++ b/mifosng-android/src/main/res/values/strings.xml @@ -282,6 +282,8 @@ Loan Officer + Sync Loan Repayment Transaction + No Loan Repayment to sync Loan Purposes Fund Staff @@ -571,7 +573,7 @@ Groups Centers LoanRepayments - SavingsAccountTransaction + Sync Savings Account Transactions Payloads : Transaction : Nothing To Sync :)