190 lines
7.1 KiB
Kotlin
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"
|
|
}
|
|
}
|