diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragment.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragment.kt index 199390a9663..30a137d1bbf 100755 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragment.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragment.kt @@ -9,30 +9,33 @@ import android.view.LayoutInflater import android.view.Menu 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 androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.mifos.core.objects.accounts.loan.LoanWithAssociations import com.mifos.core.objects.accounts.loan.RepaymentSchedule import com.mifos.mifosxdroid.R import com.mifos.mifosxdroid.adapters.LoanRepaymentScheduleAdapter +import com.mifos.mifosxdroid.core.MifosBaseFragment import com.mifos.mifosxdroid.core.ProgressableFragment import com.mifos.mifosxdroid.core.util.Toaster import com.mifos.mifosxdroid.databinding.FragmentLoanRepaymentScheduleBinding +import com.mifos.mifosxdroid.online.datatable.DataTableScreen +import com.mifos.mifosxdroid.online.loanrepayment.LoanRepaymentViewModel import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class LoanRepaymentScheduleFragment : ProgressableFragment() { +class LoanRepaymentScheduleFragment : MifosBaseFragment() { - private lateinit var binding: FragmentLoanRepaymentScheduleBinding private val arg: LoanRepaymentScheduleFragmentArgs by navArgs() + private val viewModel: LoanRepaymentScheduleViewModel by viewModels() - private lateinit var viewModel: LoanRepaymentScheduleViewModel - - private var loanAccountNumber = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - loanAccountNumber = arg.loanId - setHasOptionsMenu(false) + viewModel.loanId = arg.loanId } override fun onCreateView( @@ -40,66 +43,23 @@ class LoanRepaymentScheduleFragment : ProgressableFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentLoanRepaymentScheduleBinding.inflate(inflater, container, false) - setToolbarTitle(resources.getString(R.string.loan_repayment_schedule)) - viewModel = ViewModelProvider(this)[LoanRepaymentScheduleViewModel::class.java] - inflateRepaymentSchedule() - - viewModel.loanRepaymentScheduleUiState.observe(viewLifecycleOwner) { - when (it) { - is LoanRepaymentScheduleUiState.ShowFetchingError -> { - showProgressbar(false) - showFetchingError(it.message) - } - - is LoanRepaymentScheduleUiState.ShowLoanRepaySchedule -> { - showProgressbar(false) - showLoanRepaySchedule(it.loanWithAssociations) - } - - is LoanRepaymentScheduleUiState.ShowProgressbar -> showProgressbar(true) + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + LoanRepaymentScheduleScreen( + navigateBack = { findNavController().popBackStack() } + ) } } - - return binding.root - } - - override fun onPrepareOptionsMenu(menu: Menu) { - menu.clear() - super.onPrepareOptionsMenu(menu) - } - - private fun inflateRepaymentSchedule() { - viewModel.loadLoanRepaySchedule(loanAccountNumber) - } - - private fun showProgressbar(b: Boolean) { - showProgress(b) } - private fun showLoanRepaySchedule(loanWithAssociations: LoanWithAssociations) { - /* Activity is null - Fragment has been detached; no need to do anything. */ - if (activity == null) return - val listOfActualPeriods = loanWithAssociations - .repaymentSchedule - .getlistOfActualPeriods() - val loanRepaymentScheduleAdapter = - LoanRepaymentScheduleAdapter(requireActivity(), listOfActualPeriods) - binding.lvRepaymentSchedule.adapter = loanRepaymentScheduleAdapter - val totalRepaymentsCompleted = resources.getString(R.string.complete) + "" + - " : " - val totalRepaymentsOverdue = resources.getString(R.string.overdue) + " : " - val totalRepaymentsPending = resources.getString(R.string.pending) + " : " - //Implementing the Footer here - binding.flrsFooter.tvTotalPaid.text = totalRepaymentsCompleted + RepaymentSchedule - .getNumberOfRepaymentsComplete(listOfActualPeriods) - binding.flrsFooter.tvTotalOverdue.text = totalRepaymentsOverdue + RepaymentSchedule - .getNumberOfRepaymentsOverDue(listOfActualPeriods) - binding.flrsFooter.tvTotalUpcoming.text = totalRepaymentsPending + RepaymentSchedule - .getNumberOfRepaymentsPending(listOfActualPeriods) + override fun onResume() { + super.onResume() + toolbar?.visibility = View.GONE } - private fun showFetchingError(s: String?) { - Toaster.show(binding.root, s) + override fun onStop() { + super.onStop() + toolbar?.visibility = View.VISIBLE } -} \ No newline at end of file +} diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragmentOld.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragmentOld.kt new file mode 100644 index 00000000000..e076708a95d --- /dev/null +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleFragmentOld.kt @@ -0,0 +1,105 @@ +///* +// * This project is licensed under the open source MPL V2. +// * See https://github.com/openMF/android-client/blob/master/LICENSE.md +// */ +//package com.mifos.mifosxdroid.online.loanrepaymentschedule +// +//import android.os.Bundle +//import android.view.LayoutInflater +//import android.view.Menu +//import android.view.View +//import android.view.ViewGroup +//import androidx.lifecycle.ViewModelProvider +//import androidx.navigation.fragment.navArgs +//import com.mifos.core.objects.accounts.loan.LoanWithAssociations +//import com.mifos.core.objects.accounts.loan.RepaymentSchedule +//import com.mifos.mifosxdroid.R +//import com.mifos.mifosxdroid.adapters.LoanRepaymentScheduleAdapter +//import com.mifos.mifosxdroid.core.ProgressableFragment +//import com.mifos.mifosxdroid.core.util.Toaster +//import com.mifos.mifosxdroid.databinding.FragmentLoanRepaymentScheduleBinding +//import dagger.hilt.android.AndroidEntryPoint +// +//@AndroidEntryPoint +//class LoanRepaymentScheduleFragment : ProgressableFragment() { +// +// private lateinit var binding: FragmentLoanRepaymentScheduleBinding +// private val arg: LoanRepaymentScheduleFragmentArgs by navArgs() +// +// private lateinit var viewModel: LoanRepaymentScheduleViewModel +// +// private var loanAccountNumber = 0 +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// loanAccountNumber = arg.loanId +// setHasOptionsMenu(false) +// } +// +// override fun onCreateView( +// inflater: LayoutInflater, +// container: ViewGroup?, +// savedInstanceState: Bundle? +// ): View { +// binding = FragmentLoanRepaymentScheduleBinding.inflate(inflater, container, false) +// setToolbarTitle(resources.getString(R.string.loan_repayment_schedule)) +// viewModel = ViewModelProvider(this)[LoanRepaymentScheduleViewModel::class.java] +// inflateRepaymentSchedule() +// +// viewModel.loanRepaymentScheduleUiState.observe(viewLifecycleOwner) { +// when (it) { +// is LoanRepaymentScheduleUiState.ShowFetchingError -> { +// showProgressbar(false) +// showFetchingError(it.message) +// } +// +// is LoanRepaymentScheduleUiState.ShowLoanRepaySchedule -> { +// showProgressbar(false) +// showLoanRepaySchedule(it.loanWithAssociations) +// } +// +// is LoanRepaymentScheduleUiState.ShowProgressbar -> showProgressbar(true) +// } +// } +// +// return binding.root +// } +// +// override fun onPrepareOptionsMenu(menu: Menu) { +// menu.clear() +// super.onPrepareOptionsMenu(menu) +// } +// +// private fun inflateRepaymentSchedule() { +// viewModel.loadLoanRepaySchedule(loanAccountNumber) +// } +// +// private fun showProgressbar(b: Boolean) { +// showProgress(b) +// } +// +// private fun showLoanRepaySchedule(loanWithAssociations: LoanWithAssociations) { +// /* Activity is null - Fragment has been detached; no need to do anything. */ +// if (activity == null) return +// val listOfActualPeriods = loanWithAssociations +// .repaymentSchedule +// .getlistOfActualPeriods() +// val loanRepaymentScheduleAdapter = +// LoanRepaymentScheduleAdapter(requireActivity(), listOfActualPeriods) +// binding.lvRepaymentSchedule.adapter = loanRepaymentScheduleAdapter +// val totalRepaymentsCompleted = resources.getString(R.string.complete) + "" + +// " : " +// val totalRepaymentsOverdue = resources.getString(R.string.overdue) + " : " +// val totalRepaymentsPending = resources.getString(R.string.pending) + " : " +// //Implementing the Footer here +// binding.flrsFooter.tvTotalPaid.text = totalRepaymentsCompleted + RepaymentSchedule +// .getNumberOfRepaymentsComplete(listOfActualPeriods) +// binding.flrsFooter.tvTotalOverdue.text = totalRepaymentsOverdue + RepaymentSchedule +// .getNumberOfRepaymentsOverDue(listOfActualPeriods) +// binding.flrsFooter.tvTotalUpcoming.text = totalRepaymentsPending + RepaymentSchedule +// .getNumberOfRepaymentsPending(listOfActualPeriods) +// } +// +// private fun showFetchingError(s: String?) { +// Toaster.show(binding.root, s) +// } +//} \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleScreen.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleScreen.kt new file mode 100644 index 00000000000..c38207c0a15 --- /dev/null +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleScreen.kt @@ -0,0 +1,357 @@ +package com.mifos.mifosxdroid.online.loanrepaymentschedule + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +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.material3.HorizontalDivider +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.graphics.Color +import androidx.compose.ui.res.stringResource +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.component.MifosSweetError +import com.mifos.core.designsystem.icon.MifosIcons +import com.mifos.core.objects.accounts.loan.LoanWithAssociations +import com.mifos.core.objects.accounts.loan.Period +import com.mifos.core.objects.accounts.loan.RepaymentSchedule +import com.mifos.mifosxdroid.R +import com.mifos.utils.DateHelper + +/** + * Created by Pronay Sarker on 03/07/2024 (9:18 AM) + */ + +@Composable +fun LoanRepaymentScheduleScreen( + viewModel: LoanRepaymentScheduleViewModel = hiltViewModel(), + navigateBack: () -> Unit +) { + val uiState by viewModel.loanRepaymentScheduleUiState.collectAsStateWithLifecycle() + + LaunchedEffect(key1 = Unit) { + viewModel.loadLoanRepaySchedule() + } + + LoanRepaymentScheduleScreen( + uiState = uiState, + navigateBack = navigateBack, + onRetry = { viewModel.loadLoanRepaySchedule() } + ) +} + +@Composable +fun LoanRepaymentScheduleScreen( + uiState: LoanRepaymentScheduleUiState, + navigateBack: () -> Unit, + onRetry: () -> Unit +) { + val snackbarHostState = remember { + SnackbarHostState() + } + + MifosScaffold( + title = stringResource(R.string.loan_repayment_schedule), + snackbarHostState = snackbarHostState, + icon = MifosIcons.arrowBack, + onBackPressed = navigateBack + ) { + Box(modifier = Modifier.padding(it)) { + when (uiState) { + is LoanRepaymentScheduleUiState.ShowFetchingError -> { + MifosSweetError( + message = uiState.message, + onclick = onRetry + ) + } + + is LoanRepaymentScheduleUiState.ShowLoanRepaySchedule -> { + LoanRepaymentScheduleContent( + uiState.loanWithAssociations.repaymentSchedule.getlistOfActualPeriods() + ) + } + + LoanRepaymentScheduleUiState.ShowProgressbar -> { + MifosCircularProgress() + } + } + } + } +} + +@Composable +fun LoanRepaymentScheduleContent( + periods: List +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + HeaderLoanRepaymentSchedule() + + Box { + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items(periods) { period -> + LoanRepaymentRowItem( + color = when { + period.complete != null && period.complete!! -> { + Color.Green + } + + period.totalOverdue != null && period.totalOverdue!! > 0 -> { + Color.Red + } + + else -> Color.Blue.copy(alpha = .7f) + + }, + date = period.dueDate?.let { DateHelper.getDateAsString(it) }, + amountDue = period.totalDueForPeriod.toString(), + amountPaid = period.totalPaidForPeriod.toString() + ) + } + } + + BottomBarLoanRepaymentSchedule( + totalPaid = RepaymentSchedule.getNumberOfRepaymentsComplete(periods).toString(), + totalOverdue = RepaymentSchedule.getNumberOfRepaymentsOverDue(periods).toString(), + tvTotalUpcoming = RepaymentSchedule.getNumberOfRepaymentsPending(periods) + .toString(), + modifier = Modifier + .align(Alignment.BottomStart) + .background(color = Color.LightGray) + ) + } + } +} + + +@Composable +fun LoanRepaymentRowItem( + color: Color, + date: String?, + amountDue: String, + amountPaid: String +) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Canvas(modifier = Modifier + .size(20.dp) + .padding(2.dp), onDraw = { + drawRect( + color = color + ) + }) + + Text( + modifier = Modifier.weight(3f), + text = date ?: "", + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.End + ) + + Text( + modifier = Modifier.weight(3f), + text = amountDue, + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.End + ) + + Text( + modifier = Modifier.weight(3f), + text = amountPaid, + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.End + ) + } + + HorizontalDivider() + } +} + +@Composable +fun HeaderLoanRepaymentSchedule() { + Box( + modifier = Modifier + .background(Color.Red.copy(alpha = .5f)) + .fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(2f), + text = stringResource(id = R.string.status), + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.Start + ) + + Text( + modifier = Modifier.weight(2f), + text = stringResource(id = R.string.date), + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.Center + ) + + Text( + modifier = Modifier.weight(3f), + text = stringResource(id = R.string.loan_amount_due), + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.Center + ) + + Text( + modifier = Modifier.weight(3f), + text = stringResource(id = R.string.amount_paid), + style = MaterialTheme.typography.bodyLarge, + color = Color.Black, + textAlign = TextAlign.End + ) + } + } +} + +@Composable +fun BottomBarLoanRepaymentSchedule( + totalPaid: String, + totalOverdue: String, + tvTotalUpcoming: String, + modifier: Modifier +) { + Box(modifier = modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(3.4f), + text = stringResource(id = R.string.complete) + " : " + totalPaid, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Start + ) + + Text( + modifier = Modifier.weight(3.3f), + text = stringResource(id = R.string.pending) + " : " + tvTotalUpcoming, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center + ) + + Text( + modifier = Modifier.weight(3.3f), + text = stringResource(id = R.string.overdue) + " : " + totalOverdue, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.End + ) + } + } +} + +class LoanRepaymentSchedulePreviewProvider : + PreviewParameterProvider { + + val loanWithAssociations = LoanWithAssociations( + repaymentSchedule = RepaymentSchedule( + periods = listOf( + Period( + complete = true, + totalDueForPeriod = 123.232, + totalPaidForPeriod = 34343.3434, + dueDate = listOf(2024, 6, 1) + ), + Period( + complete = true, + totalDueForPeriod = 123.232, + totalPaidForPeriod = 34343.3434, + dueDate = listOf(2024, 6, 1) + ), + Period( + complete = true, + totalDueForPeriod = 123.232, + totalPaidForPeriod = 34343.3434, + dueDate = listOf(2024, 6, 1) + ), + Period( + complete = true, + totalDueForPeriod = 123.232, + totalPaidForPeriod = 34343.3434, + dueDate = listOf(2024, 6, 1) + ), + Period( + complete = true, + totalDueForPeriod = 123.232, + totalPaidForPeriod = 34343.3434, + dueDate = listOf(2024, 6, 1) + ) + ) + ) + ) + + override val values: Sequence + get() = sequenceOf( + LoanRepaymentScheduleUiState.ShowFetchingError("Error fetching loan repayment schedule"), + LoanRepaymentScheduleUiState.ShowProgressbar, + LoanRepaymentScheduleUiState.ShowLoanRepaySchedule(loanWithAssociations) + ) +} + +@Composable +@Preview(showSystemUi = true) +fun PreviewLoanRepaymentSchedule( + @PreviewParameter(LoanRepaymentSchedulePreviewProvider::class) loanRepaymentScheduleUiState: LoanRepaymentScheduleUiState +) { + LoanRepaymentScheduleScreen( + uiState = loanRepaymentScheduleUiState, + navigateBack = { }, + onRetry = {} + ) +} + + + diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleViewModel.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleViewModel.kt index cc3d30b1945..3d85c6ed3fa 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleViewModel.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/loanrepaymentschedule/LoanRepaymentScheduleViewModel.kt @@ -5,6 +5,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.mifos.core.objects.accounts.loan.LoanWithAssociations import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import rx.Subscriber import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers @@ -17,12 +19,12 @@ import javax.inject.Inject class LoanRepaymentScheduleViewModel @Inject constructor(private val repository: LoanRepaymentScheduleRepository) : ViewModel() { - private val _loanRepaymentScheduleUiState = MutableLiveData() + private val _loanRepaymentScheduleUiState = MutableStateFlow(LoanRepaymentScheduleUiState.ShowProgressbar) + val loanRepaymentScheduleUiState: StateFlow get() = _loanRepaymentScheduleUiState - val loanRepaymentScheduleUiState: LiveData - get() = _loanRepaymentScheduleUiState + var loanId = 0 - fun loadLoanRepaySchedule(loanId: Int) { + fun loadLoanRepaySchedule() { _loanRepaymentScheduleUiState.value = LoanRepaymentScheduleUiState.ShowProgressbar repository.getLoanRepaySchedule(loanId) .observeOn(AndroidSchedulers.mainThread())