bitsy-wallet/app/src/main/java/cy/agorise/bitsybitshareswallet/ui/transactions/TransactionsFragment.kt

190 lines
7.1 KiB
Kotlin

package cy.agorise.bitsybitshareswallet.ui.transactions
import android.graphics.Point
import android.os.Bundle
import android.view.*
import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTree
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.jakewharton.rxbinding3.appcompat.queryTextChangeEvents
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.databinding.FragmentTransactionsBinding
import cy.agorise.bitsybitshareswallet.utils.BounceTouchListener
import cy.agorise.bitsybitshareswallet.utils.Constants
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import java.util.*
import java.util.concurrent.TimeUnit
/**
* Shows the list of transactions as well as options to filter and export those transactions
* to PDF and CSV files
*/
class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSelectedListener {
private val viewModel: TransactionsViewModel by viewModels()
private var _binding: FragmentTransactionsBinding? = null
private val binding get() = _binding!!
private var isPdfRequested: Boolean = false
private var isCsvRequested: Boolean = false
private var mDisposables = CompositeDisposable()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setHasOptionsMenu(true)
_binding = FragmentTransactionsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val crashlytics = FirebaseCrashlytics.getInstance()
crashlytics.setCustomKey(Constants.CRASHLYTICS_KEY_LAST_SCREEN, TAG)
val userId = PreferenceManager.getDefaultSharedPreferences(context)
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
val transfersDetailsAdapter = TransfersDetailsAdapter(requireContext())
binding.rvTransactions.adapter = transfersDetailsAdapter
binding.rvTransactions.layoutManager = LinearLayoutManager(context)
// Configure TransactionsViewModel to fetch the transaction history
viewModel.getFilteredTransactions(userId).observe(viewLifecycleOwner) { transactions ->
if (transactions.isEmpty()) {
binding.rvTransactions.visibility = View.GONE
binding.tvEmpty.visibility = View.VISIBLE
} else {
binding.rvTransactions.visibility = View.VISIBLE
binding.tvEmpty.visibility = View.GONE
val shouldScrollUp = transactions.size - transfersDetailsAdapter.itemCount == 1
transfersDetailsAdapter.replaceAll(transactions)
// Scroll to the top only if the difference between old and new items is 1
// which most likely means a new transaction was received/sent.
if (shouldScrollUp)
binding.rvTransactions.scrollToPosition(0)
}
}
// Set custom touch listener to handle bounce/stretch effect
val bounceTouchListener = BounceTouchListener(binding.rvTransactions)
binding.rvTransactions.setOnTouchListener(bounceTouchListener)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_transactions, menu)
// Adds listener for the SearchView
val searchItem = menu.findItem(R.id.menu_search)
val searchView = searchItem.actionView as SearchView
mDisposables.add(
searchView.queryTextChangeEvents()
.skipInitialValue()
.debounce(500, TimeUnit.MILLISECONDS)
.map { it.queryText.toString().toLowerCase(Locale.getDefault()) }
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
viewModel.setFilterQuery(it)
}
)
// Adjust SearchView width to avoid pushing other menu items out of the screen
searchView.maxWidth = getScreenWidth(activity) * 3 / 5
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_filter -> {
val filterOptionsDialog = FilterOptionsDialog()
val args = Bundle()
args.putParcelable(
FilterOptionsDialog.KEY_FILTER_OPTIONS,
viewModel.getFilterOptions()
)
filterOptionsDialog.arguments = args
filterOptionsDialog.show(childFragmentManager, "filter-options-tag")
true
}
R.id.menu_export -> {
showExportOptionsDialog()
true
}
else -> super.onOptionsItemSelected(item)
}
}
/**
* Returns the screen width in pixels for the given [FragmentActivity]
*/
private fun getScreenWidth(activity: FragmentActivity?): Int {
if (activity == null)
return 200
val size = Point()
activity.windowManager.defaultDisplay.getSize(size)
return size.x
}
/**
* Gets called when the user selects some filter options in the [FilterOptionsDialog] and wants to apply them.
*/
override fun onFilterOptionsSelected(filterOptions: FilterOptions) {
viewModel.applyFilterOptions(filterOptions)
}
private fun showExportOptionsDialog() {
// Make export options selected by default
val indices = intArrayOf(0, 1)
MaterialDialog(requireContext()).show {
title(R.string.title_export_transactions)
listItemsMultiChoice(
R.array.export_options,
initialSelection = indices,
waitForPositiveButton = true
) { _, indices, _ ->
// Update export options selected
isPdfRequested = indices.contains(0)
isCsvRequested = indices.contains(1)
}
negativeButton(android.R.string.cancel) { dismiss() }
positiveButton(R.string.title_export) { getFolderForExport.launch(null) }
}
}
private val getFolderForExport = registerForActivityResult(OpenDocumentTree()) { folderUri ->
viewModel.exportFilteredTransactions(folderUri, isPdfRequested, isCsvRequested)
}
override fun onDestroy() {
super.onDestroy()
if (!mDisposables.isDisposed) mDisposables.dispose()
}
companion object {
private const val TAG = "TransactionsFragment"
}
}