From 016831ee48d6592cceb4d6cf9d9c3800934606c9 Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Fri, 24 May 2024 10:30:20 +0530 Subject: [PATCH] migrate saving accounts transaction screen to compose #2588 --- .../SavingAccountsTransactionFragment.kt | 994 +++++++++--------- .../SavingAccountsDetailFragment.kt | 2 +- .../SavingAccountsTransactionFilterDialog.kt | 496 +++++++++ .../SavingAccountsTransactionScreen.kt | 399 +++++++ .../SavingAccountsTransactionTopBar.kt | 74 ++ .../SavingAccountsTransactionViewModel.kt | 245 +++++ .../SavingsAccountTransactionFragment.kt | 64 ++ .../SavingAccountsTransactionViewModel.kt | 304 +++--- app/src/main/res/values/strings.xml | 1 + .../core/ui/component/MifosErrorComponent.kt | 33 + 10 files changed, 1962 insertions(+), 650 deletions(-) create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionScreen.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionTopBar.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionViewModel.kt create mode 100644 app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingsAccountTransactionFragment.kt diff --git a/app/src/main/java/org/mifos/mobile/ui/fragments/SavingAccountsTransactionFragment.kt b/app/src/main/java/org/mifos/mobile/ui/fragments/SavingAccountsTransactionFragment.kt index deb69d889..f1735b4b4 100644 --- a/app/src/main/java/org/mifos/mobile/ui/fragments/SavingAccountsTransactionFragment.kt +++ b/app/src/main/java/org/mifos/mobile/ui/fragments/SavingAccountsTransactionFragment.kt @@ -1,497 +1,497 @@ -package org.mifos.mobile.ui.fragments - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.os.Parcelable -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 android.widget.Button -import android.widget.RadioButton -import android.widget.RadioGroup -import android.widget.Toast -import androidx.appcompat.widget.AppCompatCheckBox -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.github.therajanmaurya.sweeterror.SweetUIErrorHandler -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import org.mifos.mobile.R -import org.mifos.mobile.databinding.FragmentSavingAccountTransactionsBinding -import org.mifos.mobile.models.CheckboxStatus -import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations -import org.mifos.mobile.models.accounts.savings.Transactions -import org.mifos.mobile.ui.activities.SavingsAccountContainerActivity -import org.mifos.mobile.ui.adapters.CheckBoxAdapter -import org.mifos.mobile.ui.adapters.SavingAccountsTransactionListAdapter -import org.mifos.mobile.ui.fragments.base.BaseFragment -import org.mifos.mobile.utils.CheckBoxStatusUtil -import org.mifos.mobile.utils.Constants -import org.mifos.mobile.utils.DateHelper -import org.mifos.mobile.utils.DatePick -import org.mifos.mobile.utils.DividerItemDecoration -import org.mifos.mobile.utils.Network -import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedParcelable -import org.mifos.mobile.utils.SavingsAccountUiState -import org.mifos.mobile.utils.StatusUtils -import org.mifos.mobile.utils.Toaster -import org.mifos.mobile.utils.getDatePickerDialog -import org.mifos.mobile.viewModels.SavingAccountsTransactionViewModel -import java.time.Instant -import javax.inject.Inject - -/** - * Created by dilpreet on 6/3/17. - */ -@AndroidEntryPoint -class SavingAccountsTransactionFragment : BaseFragment() { - - private var _binding: FragmentSavingAccountTransactionsBinding? = null - private val binding get() = _binding!! - private val viewModel: SavingAccountsTransactionViewModel by viewModels() - - @JvmField - @Inject - var transactionListAdapter: SavingAccountsTransactionListAdapter? = null - - @JvmField - @Inject - var checkBoxAdapter: CheckBoxAdapter? = null - private var tvStartDate: Button? = null - private var tvEndDate: Button? = null - private var sweetUIErrorHandler: SweetUIErrorHandler? = null - private var savingsId: Long = 0 - private var transactionsList: List? = null - private var savingsWithAssociations: SavingsWithAssociations? = null - private var datePick: DatePick? = null - private var startDate: Long = Instant.now().toEpochMilli() - private var endDate: Long = Instant.now().toEpochMilli() - private var isReady = false - private var statusList: List? = null - private var isCheckBoxPeriod = false - private var selectedRadioButtonId = 0 - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - (activity as? SavingsAccountContainerActivity)?.showToolbar() - setHasOptionsMenu(true) - setToolbarTitle(getString(R.string.saving_account_transactions_details)) - if (arguments != null) savingsId = arguments?.getLong(Constants.SAVINGS_ID)!! - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - _binding = FragmentSavingAccountTransactionsBinding.inflate(inflater, container, false) - sweetUIErrorHandler = SweetUIErrorHandler(context, binding.root) - showUserInterface() - if (savedInstanceState == null) { - viewModel.loadSavingsWithAssociations(savingsId) - } - initializeFilterVariables() - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - with(binding) { - tvHelpLineNumber.setOnClickListener { - dialHelpLineNumber() - } - layoutError.btnTryAgain.setOnClickListener { - retryClicked() - } - } - - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.savingAccountsTransactionUiState.collect { state -> - when (state) { - SavingsAccountUiState.Loading -> showProgress() - - SavingsAccountUiState.Error -> { - hideProgress() - showErrorFetchingSavingAccountsDetail(context?.getString(R.string.saving_account_details)) - } - - is SavingsAccountUiState.SuccessLoadingSavingsWithAssociations -> { - hideProgress() - showSavingAccountsDetail(state.savingAccount) - } - - is SavingsAccountUiState.ShowFilteredTransactionsList -> { - showFilteredList(state.savingAccountsTransactionList) - } - - is SavingsAccountUiState.Initial -> {} - - else -> throw IllegalStateException("Unexpected State : $state") - } - } - } - } - } - - private fun initializeFilterVariables() { - statusList = StatusUtils.getSavingsAccountTransactionList(activity) - isCheckBoxPeriod = false - isReady = false - selectedRadioButtonId = -1 - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putParcelable(Constants.SAVINGS_ACCOUNTS, savingsWithAssociations) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - if (savedInstanceState != null) { - showSavingAccountsDetail( - savedInstanceState.getCheckedParcelable( - SavingsWithAssociations::class.java, - Constants.SAVINGS_ACCOUNTS - ) - ) } - } - - /** - * Setting up basic components - */ - fun showUserInterface() { - val layoutManager = LinearLayoutManager(activity) - layoutManager.orientation = LinearLayoutManager.VERTICAL - binding.rvSavingAccountsTransaction.setHasFixedSize(true) - binding.rvSavingAccountsTransaction.layoutManager = layoutManager - binding.rvSavingAccountsTransaction.adapter = transactionListAdapter - binding.rvSavingAccountsTransaction.addItemDecoration( - DividerItemDecoration( - requireContext(), - layoutManager.orientation, - ), - ) - } - - /** - * Provides with `savingsWithAssociations` fetched from server which is used to update the - * `transactionListAdapter` - * - * @param savingsWithAssociations Contains [Transactions] for given Savings account. - */ - private fun showSavingAccountsDetail(savingsWithAssociations: SavingsWithAssociations?) { - binding.llAccount.visibility = View.VISIBLE - this.savingsWithAssociations = savingsWithAssociations - transactionsList = savingsWithAssociations?.transactions - if (transactionsList != null && transactionsList?.isNotEmpty() == true) { - transactionListAdapter?.setContext(context) - transactionListAdapter?.setSavingAccountsTransactionList(transactionsList) - } else { - showEmptyTransactions() - } - } - - /** - * It is called whenever any error occurs while executing a request - * - * @param message Error message that tells the user about the problem. - */ - private fun showErrorFetchingSavingAccountsDetail(message: String?) { - if (!Network.isConnected(activity)) { - sweetUIErrorHandler?.showSweetNoInternetUI( - binding.rvSavingAccountsTransaction, - binding.layoutError.root, - ) - } else { - sweetUIErrorHandler?.showSweetErrorUI( - message, - binding.rvSavingAccountsTransaction, - binding.layoutError.root, - ) - } - } - - private fun retryClicked() { - if (Network.isConnected(context)) { - sweetUIErrorHandler?.hideSweetErrorLayoutUI( - binding.rvSavingAccountsTransaction, - binding.layoutError.root, - ) - viewModel.loadSavingsWithAssociations(savingsId) - } else { - Toast.makeText( - context, - getString(R.string.internet_not_connected), - Toast.LENGTH_SHORT, - ).show() - } - } - - /** - * Provides with a filtered list according to the constraints used in `filter()` function - */ - private fun showFilteredList(list: List?) { - if (!list.isNullOrEmpty()) { - Toaster.show(binding.root, getString(R.string.filtered)) - transactionListAdapter?.setSavingAccountsTransactionList(list) - } else { - showEmptyTransactions() - } - } - - private fun showEmptyTransactions() { - sweetUIErrorHandler?.showSweetEmptyUI( - getString(R.string.transactions), - R.drawable.ic_compare_arrows_black_24dp, - binding.rvSavingAccountsTransaction, - binding.layoutError.root, - ) - } - - /** - * Opens up Phone Dialer - */ - private fun dialHelpLineNumber() { - val intent = Intent(Intent.ACTION_DIAL) - intent.data = Uri.parse("tel:" + getString(R.string.help_line_number)) - startActivity(intent) - } - - private fun startDatePick() { - datePick = DatePick.START - getDatePickerDialog(Instant.ofEpochMilli(startDate)) { - tvEndDate?.isEnabled = true - tvStartDate?.text = DateHelper.getDateAsStringFromLong(it) - startDate = it - }.show(requireActivity().supportFragmentManager, Constants.DFRAG_DATE_PICKER) - } - - private fun endDatePick() { - datePick = DatePick.END - getDatePickerDialog(Instant.ofEpochMilli(startDate)) { - endDate = it - tvEndDate?.text = DateHelper.getDateAsStringFromLong(it) - isReady = true - }.show(requireActivity().supportFragmentManager, Constants.DFRAG_DATE_PICKER) - } - - /** - * Checks if `startDate` is less than `endDate` - * - * @return Returns true if `startDate` is less than `endDate` - */ - private fun isEndDateLargeThanStartDate(): Boolean { - return startDate <= endDate - } - - fun showProgress() { - showProgressBar() - } - - fun hideProgress() { - hideProgressBar() - } - - /** - * Shows a filter dialog - */ - private fun showFilterDialog() { - val inflater = activity?.layoutInflater - val dialogView = inflater?.inflate(R.layout.layout_filter_dialog, null, false) - val checkBoxPeriod: AppCompatCheckBox? = dialogView?.findViewById(R.id.cb_select) - val radioGroupFilter = dialogView?.findViewById(R.id.rg_date_filter) - tvStartDate = dialogView?.findViewById(R.id.tv_start_date) - tvEndDate = dialogView?.findViewById(R.id.tv_end_date) - tvStartDate?.isEnabled = false - tvEndDate?.isEnabled = false - - tvStartDate?.text = DateHelper.getDateAsStringFromLong(startDate) - tvEndDate?.text = DateHelper.getDateAsStringFromLong(endDate) - // setup listeners - tvStartDate?.setOnClickListener { startDatePick() } - tvEndDate?.setOnClickListener { endDatePick() } - checkBoxPeriod?.setOnClickListener { - checkBoxPeriod.isChecked = (!checkBoxPeriod.isChecked) - } - checkBoxPeriod?.setOnCheckedChangeListener { _, isChecked -> - isCheckBoxPeriod = isChecked - if (!isChecked) { - isReady = false - radioGroupFilter?.clearCheck() - selectedRadioButtonId = -1 - } else { - if (selectedRadioButtonId == -1) { - val btn = dialogView.findViewById(R.id.rb_date) - btn.isChecked = true - } - } - } - radioGroupFilter?.setOnCheckedChangeListener { radioGroup, _ -> - isCheckBoxPeriod = true - selectedRadioButtonId = radioGroup.checkedRadioButtonId - when (radioGroup.checkedRadioButtonId) { - R.id.rb_four_weeks -> { - tvStartDate?.isEnabled = false - tvEndDate?.isEnabled = false - startDate = DateHelper.subtractWeeks(4) - endDate = System.currentTimeMillis() - isReady = true - } - - R.id.rb_three_months -> { - tvStartDate?.isEnabled = false - tvEndDate?.isEnabled = false - startDate = DateHelper.subtractMonths(3) - endDate = System.currentTimeMillis() - isReady = true - } - - R.id.rb_six_months -> { - tvStartDate?.isEnabled = false - tvEndDate?.isEnabled = false - startDate = DateHelper.subtractMonths(6) - endDate = System.currentTimeMillis() - isReady = true - } - - R.id.rb_date -> { - tvStartDate?.isEnabled = true - tvEndDate?.isEnabled = false - } - } - } - - // restore prev state - checkBoxPeriod?.isChecked = isCheckBoxPeriod - if (selectedRadioButtonId != -1) { - val btn = dialogView?.findViewById(selectedRadioButtonId) - btn?.isChecked = true - } - val checkBoxRecyclerView: RecyclerView? = dialogView?.findViewById(R.id.recycler_view) - val layoutManager = LinearLayoutManager(activity) - layoutManager.orientation = LinearLayoutManager.VERTICAL - checkBoxRecyclerView?.layoutManager = layoutManager - checkBoxRecyclerView?.adapter = checkBoxAdapter - checkBoxAdapter?.statusList = statusList - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.select_you_want) - .setView(dialogView) - .setPositiveButton(getString(R.string.filter)) { _, _ -> - if (checkBoxPeriod?.isChecked == true || isCheckBoxPeriod) { - if (!isReady) { - Toaster.show(binding.root, getString(R.string.select_date)) - return@setPositiveButton - } else if (!isEndDateLargeThanStartDate()) { - Toaster.show( - binding.root, - getString(R.string.end_date_must_be_greater), - ) - return@setPositiveButton - } - filter(startDate, endDate, checkBoxAdapter?.statusList) - } else { - filter(checkBoxAdapter?.statusList) - } - filterSavingsAccountTransactionsByType(checkBoxAdapter?.statusList) - } - .setNeutralButton(getString(R.string.clear_filters)) { _, _ -> - transactionListAdapter?.setSavingAccountsTransactionList(transactionsList) - initializeFilterVariables() - } - .setNegativeButton(R.string.cancel) { _, _ -> } - .create() - .show() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_saving_accounts_transaction, menu) - super.onCreateOptionsMenu(menu, inflater) - } - - /** - * Will filter `transactionsList` according to `startDate` and `endDate` - * - * @param startDate Starting date - * @param endDate Ending date - */ - private fun filter(startDate: Long?, endDate: Long?, statusModelList: List?) { - val hasOtherFilters = statusModelList?.any { it!!.isChecked } - val transactionListToFilter = if (hasOtherFilters == true) filterSavingsAccountTransactionsByType(statusModelList) else transactionsList - - viewModel.filterTransactionList( - transactionListToFilter, - startDate, - endDate, - ) - } - - /** - * Will filter `transactionsList` according to `startDate` and `endDate` - * - * @param statusModelList Status Model List - */ - private fun filter(statusModelList: List?) { - showFilteredList(filterSavingsAccountTransactionsByType(statusModelList)) - } - - /** - * Will filter `transactionsList` according to `startDate` and `endDate` - * @param statusModelList Status Model List - */ - private fun filterSavingsAccountTransactionsByType(statusModelList: List?): List { - val filteredSavingsTransactions: MutableList = ArrayList() - for (status in viewModel - .getCheckedStatus(statusModelList)!!) { - viewModel - .filterTransactionListByType(transactionsList, status, getCheckBoxStatusStrings()) - ?.let { filteredSavingsTransactions.addAll(it) } - } - return filteredSavingsTransactions - } - - private fun getCheckBoxStatusStrings(): CheckBoxStatusUtil { - return CheckBoxStatusUtil().apply { - this.depositString = context?.getString(R.string.deposit) - this.dividendPayoutString = context?.getString(R.string.dividend_payout) - this.withdrawalString = context?.getString(R.string.withdrawal) - this.interestPostingString = context?.getString(R.string.interest_posting) - this.feeDeductionString = context?.getString(R.string.fee_deduction) - this.withdrawalTransferString = context?.getString(R.string.withdrawal_transfer) - this.rejectedTransferString = context?.getString(R.string.rejected_transfer) - this.overdraftFeeString = context?.getString(R.string.overdraft_fee) - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_filter_savings_transactions -> showFilterDialog() - } - return true - } - - override fun onDestroyView() { - super.onDestroyView() - hideProgress() - _binding = null - } - - companion object { - fun newInstance(savingsId: Long?): SavingAccountsTransactionFragment { - val fragment = SavingAccountsTransactionFragment() - val args = Bundle() - if (savingsId != null) args.putLong(Constants.SAVINGS_ID, savingsId) - fragment.arguments = args - return fragment - } - } -} +//package org.mifos.mobile.ui.fragments +// +//import android.content.Intent +//import android.net.Uri +//import android.os.Bundle +//import android.os.Parcelable +//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 android.widget.Button +//import android.widget.RadioButton +//import android.widget.RadioGroup +//import android.widget.Toast +//import androidx.appcompat.widget.AppCompatCheckBox +//import androidx.fragment.app.viewModels +//import androidx.lifecycle.Lifecycle +//import androidx.lifecycle.lifecycleScope +//import androidx.lifecycle.repeatOnLifecycle +//import androidx.recyclerview.widget.LinearLayoutManager +//import androidx.recyclerview.widget.RecyclerView +//import com.github.therajanmaurya.sweeterror.SweetUIErrorHandler +//import com.google.android.material.dialog.MaterialAlertDialogBuilder +//import dagger.hilt.android.AndroidEntryPoint +//import kotlinx.coroutines.launch +//import org.mifos.mobile.R +//import org.mifos.mobile.databinding.FragmentSavingAccountTransactionsBinding +//import org.mifos.mobile.models.CheckboxStatus +//import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations +//import org.mifos.mobile.models.accounts.savings.Transactions +//import org.mifos.mobile.ui.activities.SavingsAccountContainerActivity +//import org.mifos.mobile.ui.adapters.CheckBoxAdapter +//import org.mifos.mobile.ui.adapters.SavingAccountsTransactionListAdapter +//import org.mifos.mobile.ui.fragments.base.BaseFragment +//import org.mifos.mobile.utils.CheckBoxStatusUtil +//import org.mifos.mobile.utils.Constants +//import org.mifos.mobile.utils.DateHelper +//import org.mifos.mobile.utils.DatePick +//import org.mifos.mobile.utils.DividerItemDecoration +//import org.mifos.mobile.utils.Network +//import org.mifos.mobile.utils.ParcelableAndSerializableUtils.getCheckedParcelable +//import org.mifos.mobile.utils.SavingsAccountUiState +//import org.mifos.mobile.utils.StatusUtils +//import org.mifos.mobile.utils.Toaster +//import org.mifos.mobile.utils.getDatePickerDialog +//import org.mifos.mobile.viewModels.SavingAccountsTransactionViewModel +//import java.time.Instant +//import javax.inject.Inject +// +///** +// * Created by dilpreet on 6/3/17. +// */ +//@AndroidEntryPoint +//class SavingAccountsTransactionFragment : BaseFragment() { +// +// private var _binding: FragmentSavingAccountTransactionsBinding? = null +// private val binding get() = _binding!! +// private val viewModel: SavingAccountsTransactionViewModel by viewModels() +// +// @JvmField +// @Inject +// var transactionListAdapter: SavingAccountsTransactionListAdapter? = null +// +// @JvmField +// @Inject +// var checkBoxAdapter: CheckBoxAdapter? = null +// private var tvStartDate: Button? = null +// private var tvEndDate: Button? = null +// private var sweetUIErrorHandler: SweetUIErrorHandler? = null +// private var savingsId: Long = 0 +// private var transactionsList: List? = null +// private var savingsWithAssociations: SavingsWithAssociations? = null +// private var datePick: DatePick? = null +// private var startDate: Long = Instant.now().toEpochMilli() +// private var endDate: Long = Instant.now().toEpochMilli() +// private var isReady = false +// private var statusList: List? = null +// private var isCheckBoxPeriod = false +// private var selectedRadioButtonId = 0 +// override fun onCreate(savedInstanceState: Bundle?) { +// super.onCreate(savedInstanceState) +// (activity as? SavingsAccountContainerActivity)?.showToolbar() +// setHasOptionsMenu(true) +// setToolbarTitle(getString(R.string.saving_account_transactions_details)) +// if (arguments != null) savingsId = arguments?.getLong(Constants.SAVINGS_ID)!! +// } +// +// override fun onCreateView( +// inflater: LayoutInflater, +// container: ViewGroup?, +// savedInstanceState: Bundle?, +// ): View { +// _binding = FragmentSavingAccountTransactionsBinding.inflate(inflater, container, false) +// sweetUIErrorHandler = SweetUIErrorHandler(context, binding.root) +// showUserInterface() +// if (savedInstanceState == null) { +// viewModel.loadSavingsWithAssociations(savingsId) +// } +// initializeFilterVariables() +// return binding.root +// } +// +// override fun onViewCreated(view: View, savedInstanceState: Bundle?) { +// super.onViewCreated(view, savedInstanceState) +// +// with(binding) { +// tvHelpLineNumber.setOnClickListener { +// dialHelpLineNumber() +// } +// layoutError.btnTryAgain.setOnClickListener { +// retryClicked() +// } +// } +// +// viewLifecycleOwner.lifecycleScope.launch { +// repeatOnLifecycle(Lifecycle.State.STARTED) { +// viewModel.savingAccountsTransactionUiState.collect { state -> +// when (state) { +// SavingsAccountUiState.Loading -> showProgress() +// +// SavingsAccountUiState.Error -> { +// hideProgress() +// showErrorFetchingSavingAccountsDetail(context?.getString(R.string.saving_account_details)) +// } +// +// is SavingsAccountUiState.SuccessLoadingSavingsWithAssociations -> { +// hideProgress() +// showSavingAccountsDetail(state.savingAccount) +// } +// +// is SavingsAccountUiState.ShowFilteredTransactionsList -> { +// showFilteredList(state.savingAccountsTransactionList) +// } +// +// is SavingsAccountUiState.Initial -> {} +// +// else -> throw IllegalStateException("Unexpected State : $state") +// } +// } +// } +// } +// } +// +// private fun initializeFilterVariables() { +// statusList = StatusUtils.getSavingsAccountTransactionList(activity) +// isCheckBoxPeriod = false +// isReady = false +// selectedRadioButtonId = -1 +// } +// +// override fun onSaveInstanceState(outState: Bundle) { +// super.onSaveInstanceState(outState) +// outState.putParcelable(Constants.SAVINGS_ACCOUNTS, savingsWithAssociations) +// } +// +// override fun onActivityCreated(savedInstanceState: Bundle?) { +// super.onActivityCreated(savedInstanceState) +// if (savedInstanceState != null) { +// showSavingAccountsDetail( +// savedInstanceState.getCheckedParcelable( +// SavingsWithAssociations::class.java, +// Constants.SAVINGS_ACCOUNTS +// ) +// ) } +// } +// +// /** +// * Setting up basic components +// */ +// fun showUserInterface() { +// val layoutManager = LinearLayoutManager(activity) +// layoutManager.orientation = LinearLayoutManager.VERTICAL +// binding.rvSavingAccountsTransaction.setHasFixedSize(true) +// binding.rvSavingAccountsTransaction.layoutManager = layoutManager +// binding.rvSavingAccountsTransaction.adapter = transactionListAdapter +// binding.rvSavingAccountsTransaction.addItemDecoration( +// DividerItemDecoration( +// requireContext(), +// layoutManager.orientation, +// ), +// ) +// } +// +// /** +// * Provides with `savingsWithAssociations` fetched from server which is used to update the +// * `transactionListAdapter` +// * +// * @param savingsWithAssociations Contains [Transactions] for given Savings account. +// */ +// private fun showSavingAccountsDetail(savingsWithAssociations: SavingsWithAssociations?) { +// binding.llAccount.visibility = View.VISIBLE +// this.savingsWithAssociations = savingsWithAssociations +// transactionsList = savingsWithAssociations?.transactions +// if (transactionsList != null && transactionsList?.isNotEmpty() == true) { +// transactionListAdapter?.setContext(context) +// transactionListAdapter?.setSavingAccountsTransactionList(transactionsList) +// } else { +// showEmptyTransactions() +// } +// } +// +// /** +// * It is called whenever any error occurs while executing a request +// * +// * @param message Error message that tells the user about the problem. +// */ +// private fun showErrorFetchingSavingAccountsDetail(message: String?) { +// if (!Network.isConnected(activity)) { +// sweetUIErrorHandler?.showSweetNoInternetUI( +// binding.rvSavingAccountsTransaction, +// binding.layoutError.root, +// ) +// } else { +// sweetUIErrorHandler?.showSweetErrorUI( +// message, +// binding.rvSavingAccountsTransaction, +// binding.layoutError.root, +// ) +// } +// } +// +// private fun retryClicked() { +// if (Network.isConnected(context)) { +// sweetUIErrorHandler?.hideSweetErrorLayoutUI( +// binding.rvSavingAccountsTransaction, +// binding.layoutError.root, +// ) +// viewModel.loadSavingsWithAssociations(savingsId) +// } else { +// Toast.makeText( +// context, +// getString(R.string.internet_not_connected), +// Toast.LENGTH_SHORT, +// ).show() +// } +// } +// +// /** +// * Provides with a filtered list according to the constraints used in `filter()` function +// */ +// private fun showFilteredList(list: List?) { +// if (!list.isNullOrEmpty()) { +// Toaster.show(binding.root, getString(R.string.filtered)) +// transactionListAdapter?.setSavingAccountsTransactionList(list) +// } else { +// showEmptyTransactions() +// } +// } +// +// private fun showEmptyTransactions() { +// sweetUIErrorHandler?.showSweetEmptyUI( +// getString(R.string.transactions), +// R.drawable.ic_compare_arrows_black_24dp, +// binding.rvSavingAccountsTransaction, +// binding.layoutError.root, +// ) +// } +// +// /** +// * Opens up Phone Dialer +// */ +// private fun dialHelpLineNumber() { +// val intent = Intent(Intent.ACTION_DIAL) +// intent.data = Uri.parse("tel:" + getString(R.string.help_line_number)) +// startActivity(intent) +// } +// +// private fun startDatePick() { +// datePick = DatePick.START +// getDatePickerDialog(Instant.ofEpochMilli(startDate)) { +// tvEndDate?.isEnabled = true +// tvStartDate?.text = DateHelper.getDateAsStringFromLong(it) +// startDate = it +// }.show(requireActivity().supportFragmentManager, Constants.DFRAG_DATE_PICKER) +// } +// +// private fun endDatePick() { +// datePick = DatePick.END +// getDatePickerDialog(Instant.ofEpochMilli(startDate)) { +// endDate = it +// tvEndDate?.text = DateHelper.getDateAsStringFromLong(it) +// isReady = true +// }.show(requireActivity().supportFragmentManager, Constants.DFRAG_DATE_PICKER) +// } +// +// /** +// * Checks if `startDate` is less than `endDate` +// * +// * @return Returns true if `startDate` is less than `endDate` +// */ +// private fun isEndDateLargeThanStartDate(): Boolean { +// return startDate <= endDate +// } +// +// fun showProgress() { +// showProgressBar() +// } +// +// fun hideProgress() { +// hideProgressBar() +// } +// +// /** +// * Shows a filter dialog +// */ +// private fun showFilterDialog() { +// val inflater = activity?.layoutInflater +// val dialogView = inflater?.inflate(R.layout.layout_filter_dialog, null, false) +// val checkBoxPeriod: AppCompatCheckBox? = dialogView?.findViewById(R.id.cb_select) +// val radioGroupFilter = dialogView?.findViewById(R.id.rg_date_filter) +// tvStartDate = dialogView?.findViewById(R.id.tv_start_date) +// tvEndDate = dialogView?.findViewById(R.id.tv_end_date) +// tvStartDate?.isEnabled = false +// tvEndDate?.isEnabled = false +// +// tvStartDate?.text = DateHelper.getDateAsStringFromLong(startDate) +// tvEndDate?.text = DateHelper.getDateAsStringFromLong(endDate) +// // setup listeners +// tvStartDate?.setOnClickListener { startDatePick() } +// tvEndDate?.setOnClickListener { endDatePick() } +// checkBoxPeriod?.setOnClickListener { +// checkBoxPeriod.isChecked = (!checkBoxPeriod.isChecked) +// } +// checkBoxPeriod?.setOnCheckedChangeListener { _, isChecked -> +// isCheckBoxPeriod = isChecked +// if (!isChecked) { +// isReady = false +// radioGroupFilter?.clearCheck() +// selectedRadioButtonId = -1 +// } else { +// if (selectedRadioButtonId == -1) { +// val btn = dialogView.findViewById(R.id.rb_date) +// btn.isChecked = true +// } +// } +// } +// radioGroupFilter?.setOnCheckedChangeListener { radioGroup, _ -> +// isCheckBoxPeriod = true +// selectedRadioButtonId = radioGroup.checkedRadioButtonId +// when (radioGroup.checkedRadioButtonId) { +// R.id.rb_four_weeks -> { +// tvStartDate?.isEnabled = false +// tvEndDate?.isEnabled = false +// startDate = DateHelper.subtractWeeks(4) +// endDate = System.currentTimeMillis() +// isReady = true +// } +// +// R.id.rb_three_months -> { +// tvStartDate?.isEnabled = false +// tvEndDate?.isEnabled = false +// startDate = DateHelper.subtractMonths(3) +// endDate = System.currentTimeMillis() +// isReady = true +// } +// +// R.id.rb_six_months -> { +// tvStartDate?.isEnabled = false +// tvEndDate?.isEnabled = false +// startDate = DateHelper.subtractMonths(6) +// endDate = System.currentTimeMillis() +// isReady = true +// } +// +// R.id.rb_date -> { +// tvStartDate?.isEnabled = true +// tvEndDate?.isEnabled = false +// } +// } +// } +// +// // restore prev state +// checkBoxPeriod?.isChecked = isCheckBoxPeriod +// if (selectedRadioButtonId != -1) { +// val btn = dialogView?.findViewById(selectedRadioButtonId) +// btn?.isChecked = true +// } +// val checkBoxRecyclerView: RecyclerView? = dialogView?.findViewById(R.id.recycler_view) +// val layoutManager = LinearLayoutManager(activity) +// layoutManager.orientation = LinearLayoutManager.VERTICAL +// checkBoxRecyclerView?.layoutManager = layoutManager +// checkBoxRecyclerView?.adapter = checkBoxAdapter +// checkBoxAdapter?.statusList = statusList +// MaterialAlertDialogBuilder(requireContext()) +// .setTitle(R.string.select_you_want) +// .setView(dialogView) +// .setPositiveButton(getString(R.string.filter)) { _, _ -> +// if (checkBoxPeriod?.isChecked == true || isCheckBoxPeriod) { +// if (!isReady) { +// Toaster.show(binding.root, getString(R.string.select_date)) +// return@setPositiveButton +// } else if (!isEndDateLargeThanStartDate()) { +// Toaster.show( +// binding.root, +// getString(R.string.end_date_must_be_greater), +// ) +// return@setPositiveButton +// } +// filter(startDate, endDate, checkBoxAdapter?.statusList) +// } else { +// filter(checkBoxAdapter?.statusList) +// } +// filterSavingsAccountTransactionsByType(checkBoxAdapter?.statusList) +// } +// .setNeutralButton(getString(R.string.clear_filters)) { _, _ -> +// transactionListAdapter?.setSavingAccountsTransactionList(transactionsList) +// initializeFilterVariables() +// } +// .setNegativeButton(R.string.cancel) { _, _ -> } +// .create() +// .show() +// } +// +// override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { +// inflater.inflate(R.menu.menu_saving_accounts_transaction, menu) +// super.onCreateOptionsMenu(menu, inflater) +// } +// +// /** +// * Will filter `transactionsList` according to `startDate` and `endDate` +// * +// * @param startDate Starting date +// * @param endDate Ending date +// */ +// private fun filter(startDate: Long?, endDate: Long?, statusModelList: List?) { +// val hasOtherFilters = statusModelList?.any { it!!.isChecked } +// val transactionListToFilter = if (hasOtherFilters == true) filterSavingsAccountTransactionsByType(statusModelList) else transactionsList +// +// viewModel.filterTransactionList( +// transactionListToFilter, +// startDate, +// endDate, +// ) +// } +// +// /** +// * Will filter `transactionsList` according to `startDate` and `endDate` +// * +// * @param statusModelList Status Model List +// */ +// private fun filter(statusModelList: List?) { +// showFilteredList(filterSavingsAccountTransactionsByType(statusModelList)) +// } +// +// /** +// * Will filter `transactionsList` according to `startDate` and `endDate` +// * @param statusModelList Status Model List +// */ +// private fun filterSavingsAccountTransactionsByType(statusModelList: List?): List { +// val filteredSavingsTransactions: MutableList = ArrayList() +// for (status in viewModel +// .getCheckedStatus(statusModelList)!!) { +// viewModel +// .filterTransactionListByType(transactionsList, status, getCheckBoxStatusStrings()) +// ?.let { filteredSavingsTransactions.addAll(it) } +// } +// return filteredSavingsTransactions +// } +// +// private fun getCheckBoxStatusStrings(): CheckBoxStatusUtil { +// return CheckBoxStatusUtil().apply { +// this.depositString = context?.getString(R.string.deposit) +// this.dividendPayoutString = context?.getString(R.string.dividend_payout) +// this.withdrawalString = context?.getString(R.string.withdrawal) +// this.interestPostingString = context?.getString(R.string.interest_posting) +// this.feeDeductionString = context?.getString(R.string.fee_deduction) +// this.withdrawalTransferString = context?.getString(R.string.withdrawal_transfer) +// this.rejectedTransferString = context?.getString(R.string.rejected_transfer) +// this.overdraftFeeString = context?.getString(R.string.overdraft_fee) +// } +// } +// +// override fun onOptionsItemSelected(item: MenuItem): Boolean { +// when (item.itemId) { +// R.id.menu_filter_savings_transactions -> showFilterDialog() +// } +// return true +// } +// +// override fun onDestroyView() { +// super.onDestroyView() +// hideProgress() +// _binding = null +// } +// +// companion object { +// fun newInstance(savingsId: Long?): SavingAccountsTransactionFragment { +// val fragment = SavingAccountsTransactionFragment() +// val args = Bundle() +// if (savingsId != null) args.putLong(Constants.SAVINGS_ID, savingsId) +// fragment.arguments = args +// 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 ac867c3cb..67d8a088d 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 @@ -22,7 +22,7 @@ import org.mifos.mobile.ui.enums.ChargeType 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.savings_account_transaction.SavingAccountsTransactionFragment import org.mifos.mobile.ui.savings_account_application.SavingsAccountApplicationFragment import org.mifos.mobile.ui.savings_account_withdraw.SavingsAccountWithdrawFragment import org.mifos.mobile.ui.fragments.SavingsMakeTransferFragment diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt new file mode 100644 index 000000000..602b12edb --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionFilterDialog.kt @@ -0,0 +1,496 @@ +package org.mifos.mobile.ui.savings_account_transaction + +import android.content.Context +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerDefaults +import androidx.compose.material3.DatePickerDialog +import androidx.compose.material3.DatePickerFormatter +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.unit.dp +import androidx.compose.ui.unit.sp +import org.mifos.mobile.R +import org.mifos.mobile.models.CheckboxStatus +import org.mifos.mobile.utils.DateHelper +import org.mifos.mobile.utils.DateHelper.getDateAsStringFromLong +import org.mifos.mobile.utils.DatePick +import java.time.Instant + +@Composable +fun SavingAccountsTransactionFilterDialog( + viewModel: SavingAccountsTransactionViewModel, + filterByDateAndType: (Long?, Long?, List?) -> Unit, + filterByDate: (Long?, Long?) -> Unit, + filterByType: (List?) -> Unit, + context: Context +) { + + val datePickerState = remember { mutableStateOf(false) } + val datePick: MutableState = remember { + mutableStateOf(null) + } + + val checkboxStates by viewModel.checkboxStates.collectAsState() + val selectedCheckboxOptionIndex by viewModel.selectedCheckboxIndexList.collectAsState() + + val selectedOptionIndex by viewModel.selectedOptionIndex.collectAsState() + + AlertDialog( + onDismissRequest = { }, + confirmButton = { + LazyColumn( + modifier = Modifier.height(70.dp) + .fillMaxWidth() + .padding(0.dp,), + horizontalAlignment = Alignment.End + ) { + item { + Button( + onClick = { + if(viewModel.transactionPeriodCheck.value && selectedCheckboxOptionIndex.isNotEmpty()) { + when (selectedOptionIndex) { + 2 -> { + viewModel.setStartDate(DateHelper.subtractWeeks(4)) + viewModel.setEndDate(System.currentTimeMillis()) + } + 3 -> { + viewModel.setStartDate(DateHelper.subtractMonths(3)) + viewModel.setEndDate(System.currentTimeMillis()) + } + 4 -> { + viewModel.setStartDate(DateHelper.subtractMonths(6)) + viewModel.setEndDate(System.currentTimeMillis()) + } + } + filterByDateAndType( + viewModel.startDate.value, + viewModel.endDate.value, + checkboxStates + ) + viewModel.setDialogOpen(false) + }else if (viewModel.transactionPeriodCheck.value) { + when (selectedOptionIndex) { + 2 -> { + viewModel.setStartDate(DateHelper.subtractWeeks(4)) + viewModel.setEndDate(System.currentTimeMillis()) + } + 3 -> { + viewModel.setStartDate(DateHelper.subtractMonths(3)) + viewModel.setEndDate(System.currentTimeMillis()) + } + 4 -> { + viewModel.setStartDate(DateHelper.subtractMonths(6)) + viewModel.setEndDate(System.currentTimeMillis()) + } + } + filterByDate( + viewModel.startDate.value, + viewModel.endDate.value + ) + viewModel.setDialogOpen(false) + }else if(selectedCheckboxOptionIndex.isNotEmpty()) { + filterByType(checkboxStates) + viewModel.setDialogOpen(false) + } + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary + ), + ) { + Text(text = stringResource(id = R.string.filter)) + } + Button( + onClick = { viewModel.setDialogOpen(false) }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + Text(text = stringResource(id = R.string.cancel)) + } + Button( + onClick = { + viewModel.setTransactionPeriodCheck(false) + viewModel.setSelectOptionIndex(0) + viewModel.setCheckboxStatesList(context) + viewModel.setStartDate(System.currentTimeMillis()) + viewModel.setEndDate(System.currentTimeMillis()) + viewModel.clearCheckboxIndexList() + viewModel.setDialogOpen(false) + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + Text(text = stringResource(id = R.string.clear_filters)) + } + } + } + }, + title = { + Text( + text = stringResource(id = R.string.select_you_want), + fontSize = 14.sp + ) + }, + text = { + Box(modifier = Modifier) { + SavingAccountsTransactionFilterDialogContent( + checkboxStates, + viewModel, + selectedCheckboxOptionIndex, + viewModel.radioGroup, + selectedOptionIndex, + datePickerState, + datePick, + ) + + if(datePickerState.value) { + DatePickerContent( + datePickerState = datePickerState, + datePick = datePick + ) { + if(datePick.value == DatePick.START) { + viewModel.setStartDate(it) + datePick.value = DatePick.END + }else { + viewModel.setEndDate(it) + } + } + } + } + }, + modifier = Modifier + .height(580.dp) + .padding(horizontal = 10.dp) + ) +} + + +@Composable +fun SavingAccountsTransactionFilterDialogContent( + checkboxStates: List?, + viewModel: SavingAccountsTransactionViewModel, + selectedCheckboxOptionIndex: List, + radioGroup: List, + selectedOptionIndex: Int, + datePickerState: MutableState, + datePick: MutableState +) { + val selectedOption = radioGroup[selectedOptionIndex] + + val isStartEnable = selectedOptionIndex == 1 + val isEndEnable = datePick.value == DatePick.END + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + ) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 13.dp) + ) { + Row( + modifier = Modifier + .height(height = 20.dp) + ) { + Checkbox( + checked = viewModel.transactionPeriodCheck.collectAsState().value, + onCheckedChange = { + viewModel.setTransactionPeriodCheck(!viewModel.transactionPeriodCheck.value) + viewModel.setSelectOptionIndex(if(viewModel.transactionPeriodCheck.value) 1 else 0) + }, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colorScheme.primary, + checkmarkColor = MaterialTheme.colorScheme.onPrimary, + uncheckedColor = MaterialTheme.colorScheme.onSurface + ), + modifier = Modifier.offset(x = (-15).dp, y = 0.dp) + ) + Text( + text = stringResource(id = R.string.transaction_period), + modifier = Modifier.offset(x = (-18).dp, y = 0.dp) + ) + } + } + + RadioButtonOption( + text = (stringResource(id = R.string.date)), + radioGroup = radioGroup, + selectedOption = selectedOption, + currentOptionIndex = 1, + viewModel = viewModel + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 13.dp) + ) { + Row( + modifier = Modifier + .padding(horizontal = 8.dp) + .height(15.dp) + .clickable { + if (isStartEnable) { + datePickerState.value = true + } + } + ) { + val contentColor = if (isStartEnable) { + MaterialTheme.colorScheme.primary + } else { + Color.Gray + } + Icon( + imageVector = Icons.Filled.Edit, + contentDescription = "", + tint = contentColor, + ) + Spacer(modifier = Modifier.width(5.dp)) + Text( + text = getDateAsStringFromLong(viewModel.startDate.value ?: System.currentTimeMillis()), + color = contentColor, + fontSize = 12.sp + ) + } + Row( + modifier = Modifier + .padding(horizontal = 8.dp) + .height(15.dp) + .clickable { + if (isEndEnable) { + datePickerState.value = true + } + } + ) { + val contentColor = if (isEndEnable) { + MaterialTheme.colorScheme.primary + } else { + Color.Gray + } + Icon( + imageVector = Icons.Filled.Edit, + contentDescription = "", + tint = contentColor, + ) + Spacer(modifier = Modifier.width(5.dp)) + Text( + text = getDateAsStringFromLong(viewModel.endDate.value ?: System.currentTimeMillis()), + fontSize = 12.sp, + color = contentColor + ) + } + } + + RadioButtonOption( + text = (stringResource(id = R.string.four_weeks)), + radioGroup = radioGroup, + selectedOption = selectedOption, + currentOptionIndex = 2, + viewModel = viewModel + ) + + RadioButtonOption( + text = (stringResource(id = R.string.three_months)), + radioGroup = radioGroup, + selectedOption = selectedOption, + currentOptionIndex = 3, + viewModel = viewModel + ) + + RadioButtonOption( + text = (stringResource(id = R.string.six_months)), + radioGroup = radioGroup, + selectedOption = selectedOption, + currentOptionIndex = 4, + viewModel = viewModel + ) + + checkboxStates?.take(4)?.forEachIndexed { index, checkboxStatus -> + CheckBoxButtonOption( + index = index, + selectedOptionIndex = selectedCheckboxOptionIndex, + checkboxStatus = checkboxStatus ?: CheckboxStatus("", 1, false) + ) { + if(it) { + viewModel.addCheckboxIndex(index) + } else { + viewModel.removeCheckboxIndex(index) + } + viewModel.updateCheckboxStatesList(checkboxStatus?.copy(isChecked = it)) + } + } + } + } +} + +@Composable +fun RadioButtonOption( + text: String, + radioGroup: List, + selectedOption: String, + currentOptionIndex: Int, + viewModel: SavingAccountsTransactionViewModel +){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 13.dp) + ) { + Row( + modifier = Modifier + .height(height = 20.dp) + .clickable { + viewModel.setTransactionPeriodCheck(true) + viewModel.setSelectOptionIndex(currentOptionIndex) + } + ) { + RadioButton( + selected = radioGroup[currentOptionIndex] == selectedOption , + onClick = { + viewModel.setTransactionPeriodCheck(true) + viewModel.setSelectOptionIndex(currentOptionIndex) + }, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary, + ), + modifier = Modifier.offset(x = (-15).dp, y = 0.dp) + ) + Text( + text = text, + modifier = Modifier.offset(x = (-18).dp, y = 0.dp) + ) + } + } +} + +@Composable +fun CheckBoxButtonOption( + index: Int, + checkboxStatus: CheckboxStatus, + selectedOptionIndex: List, + onCheckedChange: (Boolean) -> Unit +){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 10.dp) + ) { + Row( + modifier = Modifier + .height(height = 20.dp) + ) { + Checkbox( + checked = selectedOptionIndex.contains(index), + onCheckedChange = { + onCheckedChange(it) + }, + colors = CheckboxDefaults.colors( + checkedColor = Color(checkboxStatus.color), + checkmarkColor = MaterialTheme.colorScheme.onPrimary, + uncheckedColor = Color(checkboxStatus.color) + ), + modifier = Modifier.offset(x = (-15).dp, y = 0.dp) + ) + Text( + text = checkboxStatus.status ?: "", + modifier = Modifier.offset(x = (-18).dp, y = 0.dp) + ) + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DatePickerContent( + datePickerState: MutableState, + datePick: MutableState, + selectedStartDate: (Long?) -> Unit +) { + val state = rememberDatePickerState( + initialSelectedDateMillis = Instant.now().toEpochMilli() + ) + + Column { + DatePickerDialog( + onDismissRequest = { + datePickerState.value = false + }, + confirmButton = { + Row { + Button( + onClick = { datePickerState.value = false }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary + ), + modifier = Modifier.padding(8.dp) + ) { + Text(text = "Cancel") + } + Button( + onClick = { + datePick.value = if(datePick.value == null) DatePick.START else DatePick.END + selectedStartDate(state.selectedDateMillis) + datePickerState.value = false + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.primary + ), + modifier = Modifier.padding(8.dp) + ) { + Text(text = "Ok") + } + } + }, + modifier = Modifier.padding(horizontal = 24.dp) + ) { + Column { + DatePicker( + state = state, + ) + } + } + } +} diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionScreen.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionScreen.kt new file mode 100644 index 000000000..ce01d7b48 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionScreen.kt @@ -0,0 +1,399 @@ +package org.mifos.mobile.ui.savings_account_transaction + + +import android.content.Context +import android.content.res.Configuration +import android.widget.Toast +import androidx.compose.foundation.Image +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.material.icons.Icons +import androidx.compose.material.icons.filled.CompareArrows +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +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 org.mifos.mobile.MifosSelfServiceApp +import org.mifos.mobile.R +import org.mifos.mobile.core.ui.component.EmptyDataComponentWithModifiedMessageAndIcon +import org.mifos.mobile.core.ui.component.MifosErrorComponent +import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay +import org.mifos.mobile.core.ui.theme.MifosMobileTheme +import org.mifos.mobile.models.CheckboxStatus +import org.mifos.mobile.models.accounts.savings.SavingsWithAssociations +import org.mifos.mobile.models.accounts.savings.TransactionType +import org.mifos.mobile.models.accounts.savings.Transactions +import org.mifos.mobile.utils.CheckBoxStatusUtil +import org.mifos.mobile.utils.CurrencyUtil +import org.mifos.mobile.utils.DateHelper.getDateAsString +import org.mifos.mobile.utils.Network +import org.mifos.mobile.utils.SavingsAccountUiState + +@Composable +fun SavingsAccountTransactionScreen( + viewModel: SavingAccountsTransactionViewModel = hiltViewModel(), + navigateBack: () -> Unit, +) { + + val uiState by viewModel.savingAccountsTransactionUiState.collectAsStateWithLifecycle() + val dialogState by viewModel.isDialogOpen.collectAsStateWithLifecycle() + val context: Context = LocalContext.current + viewModel.setCheckboxStatesList(context) + + SavingsAccountTransactionScreen( + uiState = uiState, + viewModel = viewModel, + navigateBack = navigateBack, + retryConnection = { viewModel.loadSavingsWithAssociations(viewModel.savingsId) }, + dialogState = dialogState, + context = context + ) +} + +@Composable +fun SavingsAccountTransactionScreen( + uiState: SavingsAccountUiState, + viewModel: SavingAccountsTransactionViewModel, + navigateBack: () -> Unit, + retryConnection: () -> Unit, + dialogState: Boolean, + context: Context +) { + val savingAccount = rememberSaveable { mutableStateOf(SavingsWithAssociations()) } + val transactionList: List = savingAccount.value.transactions.toList() + + Column(modifier = Modifier.fillMaxSize()) { + + SavingAccountsTransactionTopBar( + navigateBack = navigateBack, + filterDialog = { viewModel.setDialogOpen(true) } + ) + + Box(modifier = Modifier.weight(1f)) { + + when (uiState) { + is SavingsAccountUiState.SuccessLoadingSavingsWithAssociations -> { + if(uiState.savingAccount.transactions.isNotEmpty()) { + savingAccount.value = uiState.savingAccount + SavingsAccountTransactionContent(transactionList = transactionList) + } else { + MifosErrorComponent(isEmptyData = true) + } + } + + is SavingsAccountUiState.Loading -> { + MifosProgressIndicatorOverlay() + } + + is SavingsAccountUiState.Error -> { + MifosErrorComponent( + isNetworkConnected = Network.isConnected(context), + isEmptyData = false, + isRetryEnabled = true, + onRetry = retryConnection + ) + } + + is SavingsAccountUiState.ShowFilteredTransactionsList -> { + if(uiState.savingAccountsTransactionList != null) { + if(uiState.savingAccountsTransactionList.isNotEmpty()) { + SavingsAccountTransactionContent( + transactionList = uiState.savingAccountsTransactionList + ) + }else { + EmptyDataComponentWithModifiedMessageAndIcon(isEmptyData = true, + message = stringResource(id = R.string.no_transaction_found), + icon = Icons.Filled.CompareArrows) + } + } else { + MifosErrorComponent(isEmptyData = true) + } + } + + is SavingsAccountUiState.Initial -> Unit + + else -> Unit + } + } + + if(dialogState) { + SavingAccountsTransactionFilterDialog( + viewModel = viewModel, + filterByDateAndType = { startDate, endDate, statusModelList -> + val transactionListToFilter = filterSavingsAccountTransactionsByType( + statusModelList, + viewModel, + transactionList, + context + ) + Toast.makeText(context, R.string.filtered, Toast.LENGTH_SHORT).show() + viewModel.filterTransactionList( + transactionListToFilter, + startDate, + endDate, + ) + }, + filterByDate = { startDate, endDate -> + Toast.makeText(context, R.string.filtered, Toast.LENGTH_SHORT).show() + viewModel.filterTransactionList( + transactionList, + startDate, + endDate, + ) + }, + filterByType = { statusModelList -> + val transactionListToFilter = filterSavingsAccountTransactionsByType( + statusModelList, + viewModel, + transactionList, + context + ) + Toast.makeText(context, R.string.filtered, Toast.LENGTH_SHORT).show() + viewModel.filterTransactionList( + transactionListToFilter, + null, + null, + ) + }, + context = context + ) + } + } +} + + + +@Composable +fun SavingsAccountTransactionListItem(transaction: Transactions?) { + val color = getColor(transaction?.transactionType) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 6.dp) + ) { + Image( + painter = painterResource( + id = if(color == ColorSelect.GREEN) R.drawable.triangular_green_view + else R.drawable.triangular_red_view), + contentDescription = stringResource(id = R.string.savings_account_transaction), + modifier = Modifier + .size(55.dp) + .padding(6.dp) + ) + Column( + modifier = Modifier.padding(4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = getDateAsString(transaction?.date), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = stringResource( + id = R.string.string_and_string, + transaction?.currency?.displaySymbol ?: transaction?.currency?.code ?: "", + CurrencyUtil.formatCurrency( + MifosSelfServiceApp.context , + transaction?.amount, + ) + ), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurface + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = transaction?.transactionType?.value ?: "", + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.alpha(0.7f), + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = stringResource( + id = R.string.string_and_string, + transaction?.currency?.displaySymbol ?: transaction?.currency?.code ?: "", + CurrencyUtil.formatCurrency( + MifosSelfServiceApp.context , + transaction?.runningBalance , + ) + ), + style = MaterialTheme.typography.labelMedium, + modifier = Modifier + .alpha(0.7f), + color = MaterialTheme.colorScheme.onSurface + ) + } + Row( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = transaction?.paymentDetailData?.paymentType?.name.toString(), + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.alpha(0.7f), + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } +} + +private fun filterSavingsAccountTransactionsByType( + statusModelList: List?, + viewModel: SavingAccountsTransactionViewModel, + transactionsList: List, + context: Context? + ): List { + val filteredSavingsTransactions: MutableList = ArrayList() + for (status in viewModel + .getCheckedStatus(statusModelList)!!) { + viewModel + .filterTransactionListByType(transactionsList, status, getCheckBoxStatusStrings(context)) + ?.let { filteredSavingsTransactions.addAll(it) } + } + return filteredSavingsTransactions +} + +private fun getCheckBoxStatusStrings(context: Context?): CheckBoxStatusUtil { + return CheckBoxStatusUtil().apply { + this.depositString = context?.getString(R.string.deposit) + this.dividendPayoutString = context?.getString(R.string.dividend_payout) + this.withdrawalString = context?.getString(R.string.withdrawal) + this.interestPostingString = context?.getString(R.string.interest_posting) + this.feeDeductionString = context?.getString(R.string.fee_deduction) + this.withdrawalTransferString = context?.getString(R.string.withdrawal_transfer) + this.rejectedTransferString = context?.getString(R.string.rejected_transfer) + this.overdraftFeeString = context?.getString(R.string.overdraft_fee) + } +} + + + +@Composable +fun SavingsAccountTransactionContent( + transactionList: List, +) { + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + LazyColumn { + items(items = transactionList) { + SavingsAccountTransactionListItem(it) + Divider( + thickness = 1.dp, + color = Color.Gray, + modifier = Modifier.padding(vertical = 3.dp) + ) + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(5.dp), + verticalAlignment = Alignment.Bottom + ) { + Text(text = stringResource(id = R.string.need_help), + color = MaterialTheme.colorScheme.onSurface) + Text(text = stringResource(id = R.string.help_line_number), + color = MaterialTheme.colorScheme.primary) + } + } +} + +private enum class ColorSelect { + RED, GREEN +} + +private fun getColor(transactionType: TransactionType?): ColorSelect { + if (transactionType?.deposit == true) { + return ColorSelect.GREEN + } + if (transactionType?.dividendPayout == true) { + return ColorSelect.RED + } + if (transactionType?.withdrawal == true) { + return ColorSelect.RED + } + if (transactionType?.interestPosting == true) { + return ColorSelect.GREEN + } + if (transactionType?.feeDeduction == true) { + return ColorSelect.RED + } + if (transactionType?.initiateTransfer == true) { + return ColorSelect.RED + } + if (transactionType?.approveTransfer == true) { + return ColorSelect.RED + } + if (transactionType?.withdrawTransfer == true) { + return ColorSelect.RED + } + if (transactionType?.rejectTransfer == true) { + return ColorSelect.GREEN + } + return if (transactionType?.overdraftFee == true) { + ColorSelect.RED + } else { + ColorSelect.GREEN + } +} + +class SavingsAccountTransactionUiStatesParameterProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + SavingsAccountUiState.SuccessLoadingSavingsWithAssociations(SavingsWithAssociations()), + SavingsAccountUiState.Error, + SavingsAccountUiState.Error, + SavingsAccountUiState.Loading, + ) +} + + +@Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun SavingsAccountTransactionScreenPreview( + @PreviewParameter(SavingsAccountTransactionUiStatesParameterProvider::class) savingsAccountUiState: SavingsAccountUiState +) { + MifosMobileTheme { + SavingsAccountTransactionScreen( + viewModel = hiltViewModel(), + navigateBack = {} + ) + } +} + diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionTopBar.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionTopBar.kt new file mode 100644 index 000000000..c881cea33 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionTopBar.kt @@ -0,0 +1,74 @@ +package org.mifos.mobile.ui.savings_account_transaction + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.FilterList +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.mifos.mobile.R +import org.mifos.mobile.core.ui.theme.MifosMobileTheme + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SavingAccountsTransactionTopBar( + navigateBack: () -> Unit, + filterDialog: () -> Unit +) { + + TopAppBar( + modifier = Modifier, + title = { Text(text = stringResource(id = R.string.savings_account_transaction) + , fontSize = 18.sp) }, + navigationIcon = { + IconButton( + onClick = { navigateBack.invoke() } + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back Arrow", + tint = MaterialTheme.colorScheme.onSurface + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + actions = { + IconButton(onClick = { filterDialog() }) { + Icon( + imageVector = Icons.Filled.FilterList, + contentDescription = "Menu", + tint = MaterialTheme.colorScheme.onSurface + ) + } + } + ) +} + +@Preview +@Composable +fun LoanAccountDetailTopBarPreview() { + MifosMobileTheme { + SavingAccountsTransactionTopBar({},{}) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionViewModel.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionViewModel.kt new file mode 100644 index 000000000..8f94ac111 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingAccountsTransactionViewModel.kt @@ -0,0 +1,245 @@ +package org.mifos.mobile.ui.savings_account_transaction + + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.reactivex.Observable +import io.reactivex.functions.Predicate +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import org.mifos.mobile.models.CheckboxStatus +import org.mifos.mobile.models.accounts.savings.Transactions +import org.mifos.mobile.repositories.SavingsAccountRepository +import org.mifos.mobile.utils.CheckBoxStatusUtil +import org.mifos.mobile.utils.Constants +import org.mifos.mobile.utils.DateHelper +import org.mifos.mobile.utils.SavingsAccountUiState +import org.mifos.mobile.utils.StatusUtils +import java.time.Instant +import javax.inject.Inject + +@HiltViewModel +class SavingAccountsTransactionViewModel @Inject constructor(private val savingsAccountRepositoryImp: SavingsAccountRepository) : + ViewModel() { + + private val _savingAccountsTransactionUiState = + MutableStateFlow(SavingsAccountUiState.Initial) + val savingAccountsTransactionUiState: StateFlow get() = _savingAccountsTransactionUiState + + private var _savingsId: Long = 0 + val savingsId get() = _savingsId + + private val _isDialogOpen = MutableStateFlow(false) + val isDialogOpen: StateFlow get() = _isDialogOpen + + private val _startDate = + MutableStateFlow(Instant.now().toEpochMilli()) + val startDate: StateFlow get() = _startDate + + private val _endDate = + MutableStateFlow(Instant.now().toEpochMilli()) + val endDate: StateFlow get() = _endDate + + val radioGroup = listOf("", "date", "fourWeeks", "threeMonths", "sixMonths") + + private val _selectedOptionIndex = + MutableStateFlow(0) + val selectedOptionIndex: StateFlow get() = _selectedOptionIndex + + private val _checkboxStates = + MutableStateFlow?>(null) + + val checkboxStates:StateFlow?> get() = _checkboxStates + + private val _transactionPeriodCheck = + MutableStateFlow(false) + + val transactionPeriodCheck:StateFlow get() = _transactionPeriodCheck + + private val _selectedCheckboxIndexList = + MutableStateFlow>(emptyList()) + + val selectedCheckboxIndexList:StateFlow> get() = _selectedCheckboxIndexList + + fun setSavingsId(savingsId: Long) { + _savingsId = savingsId + loadSavingsWithAssociations(savingsId) + } + /** + * Filters [List] of [CheckboxStatus] + * @param statusModelList [List] of [CheckboxStatus] + * @return Returns [List] of [CheckboxStatus] which have + * `checkboxStatus.isChecked()` as true. + */ + fun getCheckedStatus(statusModelList: List?): List? { + return Observable.fromIterable(statusModelList) + .filter { (_, _, isChecked) -> isChecked }.toList().blockingGet() + } + + /** + * Load details of a particular saving account from the server and notify the view + * to display it. Notify the view, in case there is any error in fetching + * the details from server. + * + * @param accountId Id of Savings Account + */ + fun loadSavingsWithAssociations(accountId: Long) { + viewModelScope.launch { + _savingAccountsTransactionUiState.value = SavingsAccountUiState.Loading + savingsAccountRepositoryImp.getSavingsWithAssociations( + accountId, + Constants.TRANSACTIONS, + ).catch { + _savingAccountsTransactionUiState.value = SavingsAccountUiState.Error + }.collect { + _savingAccountsTransactionUiState.value = + SavingsAccountUiState.SuccessLoadingSavingsWithAssociations(it) + } + } + } + + /** + * Used for filtering [List] of [Transactions] according to `startDate` and + * `lastDate` + * + * @param savingAccountsTransactionList [List] of [Transactions] + * @param startDate Starting date for filtering + * @param lastDate Last date for filtering + */ + fun filterTransactionList( + savingAccountsTransactionList: List?, + startDate: Long?, + lastDate: Long?, + ) { + if(startDate == null && lastDate == null) { + _savingAccountsTransactionUiState.value = SavingsAccountUiState.ShowFilteredTransactionsList(savingAccountsTransactionList) + return + } + val list = when { + (startDate != null && lastDate != null) -> { + Observable.fromIterable(savingAccountsTransactionList) + .filter { (_, _, _, _, date) -> + (DateHelper.getDateAsLongFromList(date) in startDate..lastDate) + } + .toList().blockingGet() + } + + else -> null + } + _savingAccountsTransactionUiState.value = + SavingsAccountUiState.ShowFilteredTransactionsList(list) + } + + /** + * Filters [List] of [Transactions] according to [CheckboxStatus] + * @param savingAccountsTransactionList [List] of filtered [Transactions] + * @param status Used for filtering the [List] + * @return Returns [List] of filtered [Transactions] according to the + * `status` provided. + */ + fun filterTransactionListByType( + savingAccountsTransactionList: List?, + status: CheckboxStatus?, + checkBoxStatusUtil: CheckBoxStatusUtil + ): Collection? { + return Observable.fromIterable(savingAccountsTransactionList) + .filter( + Predicate { (_, transactionType) -> + + when { + ((checkBoxStatusUtil.depositString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.deposit!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.dividendPayoutString?.let { + status?.status?.compareTo( + it + ) + } == 0) && (transactionType?.dividendPayout!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.withdrawalString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.withdrawal!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.interestPostingString?.let { + status?.status?.compareTo( + it + ) + } == 0) && (transactionType?.interestPosting!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.feeDeductionString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.feeDeduction!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.withdrawalTransferString?.let { + status?.status?.compareTo( + it + ) + } == 0) && (transactionType?.withdrawTransfer!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.rejectedTransferString?.let { + status?.status?.compareTo( + it + ) + } == 0) && (transactionType?.rejectTransfer!!)) -> + return@Predicate true + + ((checkBoxStatusUtil.overdraftFeeString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.overdraftFee!!)) -> + return@Predicate true + + else -> false + } + }, + ).toList().blockingGet() + } + + fun setDialogOpen(isOpen: Boolean) { + _isDialogOpen.value = isOpen + } + + fun setStartDate(startDate: Long?) { + _startDate.value = startDate + } + + fun setEndDate(endDate: Long?) { + _endDate.value = endDate + } + + fun setSelectOptionIndex(index: Int) { + _selectedOptionIndex.value = index + } + + fun setCheckboxStatesList(context: Context?) { + _checkboxStates.value = StatusUtils.getSavingsAccountTransactionList(context) + } + + fun updateCheckboxStatesList(status: CheckboxStatus?) { + _checkboxStates.value = _checkboxStates.value?.map { checkboxStatus -> + if (checkboxStatus?.status == status?.status) { + checkboxStatus?.isChecked = status?.isChecked ?: false + } + checkboxStatus + } + } + + fun addCheckboxIndex(index: Int) { + _selectedCheckboxIndexList.value += index + } + + fun removeCheckboxIndex(index: Int) { + _selectedCheckboxIndexList.value -= index + } + + fun clearCheckboxIndexList() { + _selectedCheckboxIndexList.value = emptyList() + } + + fun setTransactionPeriodCheck(check: Boolean) { + _transactionPeriodCheck.value = check + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingsAccountTransactionFragment.kt b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingsAccountTransactionFragment.kt new file mode 100644 index 000000000..3927195c6 --- /dev/null +++ b/app/src/main/java/org/mifos/mobile/ui/savings_account_transaction/SavingsAccountTransactionFragment.kt @@ -0,0 +1,64 @@ +package org.mifos.mobile.ui.savings_account_transaction + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.mifos.mobile.core.ui.component.mifosComposeView +import org.mifos.mobile.ui.activities.SavingsAccountContainerActivity +import org.mifos.mobile.ui.fragments.base.BaseFragment +import org.mifos.mobile.utils.Constants + + +@AndroidEntryPoint +class SavingAccountsTransactionFragment: BaseFragment() { + + private val viewModel: SavingAccountsTransactionViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (activity as? SavingsAccountContainerActivity)?.hideToolbar() + if (arguments != null) viewModel.setSavingsId(arguments?.getLong(Constants.SAVINGS_ID)!!) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + if (savedInstanceState == null) { + viewModel.loadSavingsWithAssociations(viewModel.savingsId) + } + return mifosComposeView(requireContext()) { + activity?.let { + SavingsAccountTransactionScreen( + navigateBack = { activity?.supportFragmentManager?.popBackStack() }, + ) + } + } + } + + +// private fun filterSavingsAccountTransactionsByType(statusModelList: List?): List { +// val filteredSavingsTransactions: MutableList = ArrayList() +// for (status in viewModel +// .getCheckedStatus(statusModelList)!!) { +// viewModel +// .filterTransactionListByType(transactionsList, status, getCheckBoxStatusStrings()) +// ?.let { filteredSavingsTransactions.addAll(it) } +// } +// return filteredSavingsTransactions +// } + + companion object { + fun newInstance(savingsId: Long?): SavingAccountsTransactionFragment { + val fragment = SavingAccountsTransactionFragment() + val args = Bundle() + if (savingsId != null) args.putLong(Constants.SAVINGS_ID, savingsId) + fragment.arguments = args + return fragment + } + } +} diff --git a/app/src/main/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModel.kt b/app/src/main/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModel.kt index 58c6fb597..772624ad8 100644 --- a/app/src/main/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModel.kt +++ b/app/src/main/java/org/mifos/mobile/viewModels/SavingAccountsTransactionViewModel.kt @@ -1,152 +1,152 @@ -package org.mifos.mobile.viewModels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import io.reactivex.Observable -import io.reactivex.functions.Predicate -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.launch -import org.mifos.mobile.models.CheckboxStatus -import org.mifos.mobile.models.accounts.savings.Transactions -import org.mifos.mobile.repositories.SavingsAccountRepository -import org.mifos.mobile.utils.CheckBoxStatusUtil -import org.mifos.mobile.utils.Constants -import org.mifos.mobile.utils.DateHelper -import org.mifos.mobile.utils.SavingsAccountUiState -import javax.inject.Inject - -@HiltViewModel -class SavingAccountsTransactionViewModel @Inject constructor(private val savingsAccountRepositoryImp: SavingsAccountRepository) : - ViewModel() { - - private val _savingAccountsTransactionUiState = - MutableStateFlow(SavingsAccountUiState.Initial) - val savingAccountsTransactionUiState: StateFlow get() = _savingAccountsTransactionUiState - - /** - * Filters [List] of [CheckboxStatus] - * @param statusModelList [List] of [CheckboxStatus] - * @return Returns [List] of [CheckboxStatus] which have - * `checkboxStatus.isChecked()` as true. - */ - fun getCheckedStatus(statusModelList: List?): List? { - return Observable.fromIterable(statusModelList) - .filter { (_, _, isChecked) -> isChecked }.toList().blockingGet() - } - - /** - * Load details of a particular saving account from the server and notify the view - * to display it. Notify the view, in case there is any error in fetching - * the details from server. - * - * @param accountId Id of Savings Account - */ - fun loadSavingsWithAssociations(accountId: Long) { - viewModelScope.launch { - _savingAccountsTransactionUiState.value = SavingsAccountUiState.Loading - savingsAccountRepositoryImp.getSavingsWithAssociations( - accountId, - Constants.TRANSACTIONS, - ).catch { - _savingAccountsTransactionUiState.value = SavingsAccountUiState.Error - }.collect { - _savingAccountsTransactionUiState.value = - SavingsAccountUiState.SuccessLoadingSavingsWithAssociations(it) - } - } - } - - /** - * Used for filtering [List] of [Transactions] according to `startDate` and - * `lastDate` - * - * @param savingAccountsTransactionList [List] of [Transactions] - * @param startDate Starting date for filtering - * @param lastDate Last date for filtering - */ - fun filterTransactionList( - savingAccountsTransactionList: List?, - startDate: Long?, - lastDate: Long?, - ) { - val list = when { - (startDate != null && lastDate != null) -> { - Observable.fromIterable(savingAccountsTransactionList) - .filter { (_, _, _, _, date) -> - (DateHelper.getDateAsLongFromList(date) in startDate..lastDate) - } - .toList().blockingGet() - } - - else -> null - } - _savingAccountsTransactionUiState.value = - SavingsAccountUiState.ShowFilteredTransactionsList(list) - } - - /** - * Filters [List] of [Transactions] according to [CheckboxStatus] - * @param savingAccountsTransactionList [List] of filtered [Transactions] - * @param status Used for filtering the [List] - * @return Returns [List] of filtered [Transactions] according to the - * `status` provided. - */ - fun filterTransactionListByType( - savingAccountsTransactionList: List?, - status: CheckboxStatus?, - checkBoxStatusUtil: CheckBoxStatusUtil - ): Collection? { - return Observable.fromIterable(savingAccountsTransactionList) - .filter( - Predicate { (_, transactionType) -> - - when { - ((checkBoxStatusUtil.depositString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.deposit!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.dividendPayoutString?.let { - status?.status?.compareTo( - it - ) - } == 0) && (transactionType?.dividendPayout!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.withdrawalString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.withdrawal!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.interestPostingString?.let { - status?.status?.compareTo( - it - ) - } == 0) && (transactionType?.interestPosting!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.feeDeductionString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.feeDeduction!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.withdrawalTransferString?.let { - status?.status?.compareTo( - it - ) - } == 0) && (transactionType?.withdrawTransfer!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.rejectedTransferString?.let { - status?.status?.compareTo( - it - ) - } == 0) && (transactionType?.rejectTransfer!!)) -> - return@Predicate true - - ((checkBoxStatusUtil.overdraftFeeString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.overdraftFee!!)) -> - return@Predicate true - - else -> false - } - }, - ).toList().blockingGet() - } -} \ No newline at end of file +//package org.mifos.mobile.viewModels +// +//import androidx.lifecycle.ViewModel +//import androidx.lifecycle.viewModelScope +//import dagger.hilt.android.lifecycle.HiltViewModel +//import io.reactivex.Observable +//import io.reactivex.functions.Predicate +//import kotlinx.coroutines.flow.MutableStateFlow +//import kotlinx.coroutines.flow.StateFlow +//import kotlinx.coroutines.flow.catch +//import kotlinx.coroutines.launch +//import org.mifos.mobile.models.CheckboxStatus +//import org.mifos.mobile.models.accounts.savings.Transactions +//import org.mifos.mobile.repositories.SavingsAccountRepository +//import org.mifos.mobile.utils.CheckBoxStatusUtil +//import org.mifos.mobile.utils.Constants +//import org.mifos.mobile.utils.DateHelper +//import org.mifos.mobile.utils.SavingsAccountUiState +//import javax.inject.Inject +// +//@HiltViewModel +//class SavingAccountsTransactionViewModel @Inject constructor(private val savingsAccountRepositoryImp: SavingsAccountRepository) : +// ViewModel() { +// +// private val _savingAccountsTransactionUiState = +// MutableStateFlow(SavingsAccountUiState.Initial) +// val savingAccountsTransactionUiState: StateFlow get() = _savingAccountsTransactionUiState +// +// /** +// * Filters [List] of [CheckboxStatus] +// * @param statusModelList [List] of [CheckboxStatus] +// * @return Returns [List] of [CheckboxStatus] which have +// * `checkboxStatus.isChecked()` as true. +// */ +// fun getCheckedStatus(statusModelList: List?): List? { +// return Observable.fromIterable(statusModelList) +// .filter { (_, _, isChecked) -> isChecked }.toList().blockingGet() +// } +// +// /** +// * Load details of a particular saving account from the server and notify the view +// * to display it. Notify the view, in case there is any error in fetching +// * the details from server. +// * +// * @param accountId Id of Savings Account +// */ +// fun loadSavingsWithAssociations(accountId: Long) { +// viewModelScope.launch { +// _savingAccountsTransactionUiState.value = SavingsAccountUiState.Loading +// savingsAccountRepositoryImp.getSavingsWithAssociations( +// accountId, +// Constants.TRANSACTIONS, +// ).catch { +// _savingAccountsTransactionUiState.value = SavingsAccountUiState.Error +// }.collect { +// _savingAccountsTransactionUiState.value = +// SavingsAccountUiState.SuccessLoadingSavingsWithAssociations(it) +// } +// } +// } +// +// /** +// * Used for filtering [List] of [Transactions] according to `startDate` and +// * `lastDate` +// * +// * @param savingAccountsTransactionList [List] of [Transactions] +// * @param startDate Starting date for filtering +// * @param lastDate Last date for filtering +// */ +// fun filterTransactionList( +// savingAccountsTransactionList: List?, +// startDate: Long?, +// lastDate: Long?, +// ) { +// val list = when { +// (startDate != null && lastDate != null) -> { +// Observable.fromIterable(savingAccountsTransactionList) +// .filter { (_, _, _, _, date) -> +// (DateHelper.getDateAsLongFromList(date) in startDate..lastDate) +// } +// .toList().blockingGet() +// } +// +// else -> null +// } +// _savingAccountsTransactionUiState.value = +// SavingsAccountUiState.ShowFilteredTransactionsList(list) +// } +// +// /** +// * Filters [List] of [Transactions] according to [CheckboxStatus] +// * @param savingAccountsTransactionList [List] of filtered [Transactions] +// * @param status Used for filtering the [List] +// * @return Returns [List] of filtered [Transactions] according to the +// * `status` provided. +// */ +// fun filterTransactionListByType( +// savingAccountsTransactionList: List?, +// status: CheckboxStatus?, +// checkBoxStatusUtil: CheckBoxStatusUtil +// ): Collection? { +// return Observable.fromIterable(savingAccountsTransactionList) +// .filter( +// Predicate { (_, transactionType) -> +// +// when { +// ((checkBoxStatusUtil.depositString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.deposit!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.dividendPayoutString?.let { +// status?.status?.compareTo( +// it +// ) +// } == 0) && (transactionType?.dividendPayout!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.withdrawalString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.withdrawal!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.interestPostingString?.let { +// status?.status?.compareTo( +// it +// ) +// } == 0) && (transactionType?.interestPosting!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.feeDeductionString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.feeDeduction!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.withdrawalTransferString?.let { +// status?.status?.compareTo( +// it +// ) +// } == 0) && (transactionType?.withdrawTransfer!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.rejectedTransferString?.let { +// status?.status?.compareTo( +// it +// ) +// } == 0) && (transactionType?.rejectTransfer!!)) -> +// return@Predicate true +// +// ((checkBoxStatusUtil.overdraftFeeString?.let { status?.status?.compareTo(it) } == 0) && (transactionType?.overdraftFee!!)) -> +// return@Predicate true +// +// else -> false +// } +// }, +// ).toList().blockingGet() +// } +//} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c778c1657..c377131ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -662,4 +662,5 @@ Guarantor deleted successfully Guarantor created successfully Guarantor updated successfully + No Transaction Found diff --git a/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosErrorComponent.kt b/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosErrorComponent.kt index 7fd5a2fb3..6014e5f03 100644 --- a/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosErrorComponent.kt +++ b/ui/src/main/java/org/mifos/mobile/core/ui/component/MifosErrorComponent.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CompareArrows import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.WifiOff import androidx.compose.material3.FilledTonalButton @@ -18,6 +19,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign @@ -106,6 +108,37 @@ fun EmptyDataComponent( } } +@Composable +fun EmptyDataComponentWithModifiedMessageAndIcon( + modifier: Modifier = Modifier.fillMaxSize(), + isEmptyData: Boolean = false, + message: String, + icon: ImageVector +) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + modifier = Modifier + .size(100.dp) + .padding(bottom = 12.dp), + imageVector = if(isEmptyData) icon else Icons.Filled.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondary + ) + + Text( + modifier= Modifier.padding(horizontal = 20.dp), + text = if(isEmptyData) message else stringResource(id = R.string.something_went_wrong), + style = TextStyle(fontSize = 20.sp), + color = MaterialTheme.colorScheme.onSecondary, + textAlign = TextAlign.Center + ) + } +} + @Preview(showSystemUi = true) @Composable