Refactored TransactionsFragment, by moving all the transactions filtering logic to the corresponding ViewModel and taking advantage of Kotlin coroutines to excecute the filtering process in the background.
This commit is contained in:
parent
0144938052
commit
1eb9b64b45
2 changed files with 133 additions and 100 deletions
|
@ -28,9 +28,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
|||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.fragment_transactions.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
/**
|
||||
* Shows the list of transactions as well as options to filter and export those transactions
|
||||
|
@ -44,15 +42,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
|
||||
}
|
||||
|
||||
private lateinit var mTransactionsViewModel: TransactionsViewModel
|
||||
|
||||
private lateinit var transfersDetailsAdapter: TransfersDetailsAdapter
|
||||
|
||||
private val transfersDetails = ArrayList<TransferDetail>()
|
||||
private val filteredTransfersDetails = ArrayList<TransferDetail>()
|
||||
|
||||
/** Variables used to filter the transaction items */
|
||||
private var mFilterOptions = FilterOptions()
|
||||
private lateinit var mViewModel: TransactionsViewModel
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
|
||||
|
@ -70,36 +60,29 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
val userId = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
|
||||
|
||||
transfersDetailsAdapter = TransfersDetailsAdapter(context!!)
|
||||
val transfersDetailsAdapter = TransfersDetailsAdapter(context!!)
|
||||
rvTransactions.adapter = transfersDetailsAdapter
|
||||
rvTransactions.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
// Configure TransactionsViewModel to fetch the transaction history
|
||||
mTransactionsViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java)
|
||||
mViewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java)
|
||||
|
||||
mTransactionsViewModel.getAll(userId).observe(this, Observer<List<TransferDetail>> { transfersDetails ->
|
||||
this.transfersDetails.clear()
|
||||
this.transfersDetails.addAll(transfersDetails)
|
||||
applyFilterOptions(false)
|
||||
|
||||
if (transfersDetails.isEmpty()) {
|
||||
mViewModel.getFilteredTransactions(userId).observe(this,
|
||||
Observer<List<TransferDetail>> { transactions ->
|
||||
if (transactions.isEmpty()) {
|
||||
rvTransactions.visibility = View.GONE
|
||||
tvEmpty.visibility = View.VISIBLE
|
||||
} else {
|
||||
rvTransactions.visibility = View.VISIBLE
|
||||
tvEmpty.visibility = View.GONE
|
||||
|
||||
transfersDetailsAdapter.replaceAll(transactions)
|
||||
}
|
||||
})
|
||||
|
||||
// Set custom touch listener to handle bounce/stretch effect
|
||||
val bounceTouchListener = BounceTouchListener(rvTransactions)
|
||||
rvTransactions.setOnTouchListener(bounceTouchListener)
|
||||
|
||||
// Initialize filter options
|
||||
val calendar = Calendar.getInstance()
|
||||
mFilterOptions.endDate = calendar.timeInMillis
|
||||
calendar.add(Calendar.MONTH, -2)
|
||||
mFilterOptions.startDate = calendar.timeInMillis
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
|
@ -115,8 +98,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
.map { it.queryText.toString().toLowerCase() }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
mFilterOptions.query = it
|
||||
applyFilterOptions()
|
||||
mViewModel.setFilterQuery(it)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -129,7 +111,7 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
R.id.menu_filter -> {
|
||||
val filterOptionsDialog = FilterOptionsDialog()
|
||||
val args = Bundle()
|
||||
args.putParcelable(FilterOptionsDialog.KEY_FILTER_OPTIONS, mFilterOptions)
|
||||
args.putParcelable(FilterOptionsDialog.KEY_FILTER_OPTIONS, mViewModel.getFilterOptions())
|
||||
filterOptionsDialog.arguments = args
|
||||
filterOptionsDialog.show(childFragmentManager, "filter-options-tag")
|
||||
true
|
||||
|
@ -154,68 +136,11 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
return size.x
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the TransferDetail list given the user selected filter options.
|
||||
* TODO move this to a background thread
|
||||
*/
|
||||
private fun applyFilterOptions(scrollToTop: Boolean = true) {
|
||||
// Clean the filtered list
|
||||
filteredTransfersDetails.clear()
|
||||
|
||||
// Make sure the filter dates use the same format as the transactions' dates
|
||||
val startDate = mFilterOptions.startDate / 1000
|
||||
val endDate = mFilterOptions.endDate / 1000
|
||||
|
||||
for (transferDetail in transfersDetails) {
|
||||
// Filter by transfer direction
|
||||
if (transferDetail.direction) { // Transfer sent
|
||||
if (mFilterOptions.transactionsDirection == 1)
|
||||
// Looking for received transfers only
|
||||
continue
|
||||
} else { // Transfer received
|
||||
if (mFilterOptions.transactionsDirection == 2)
|
||||
// Looking for sent transactions only
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
if (!mFilterOptions.dateRangeAll && (transferDetail.date < startDate ||
|
||||
transferDetail.date > endDate))
|
||||
continue
|
||||
|
||||
// Filter by asset
|
||||
if (!mFilterOptions.assetAll && transferDetail.assetSymbol != mFilterOptions.asset)
|
||||
continue
|
||||
|
||||
// Filter by equivalent value
|
||||
if (!mFilterOptions.equivalentValueAll && ((transferDetail.fiatAmount ?: -1 ) < mFilterOptions.fromEquivalentValue
|
||||
|| (transferDetail.fiatAmount ?: -1) > mFilterOptions.toEquivalentValue))
|
||||
continue
|
||||
|
||||
// Filter transactions sent to agorise
|
||||
if (mFilterOptions.agoriseFees && transferDetail.to.equals("agorise"))
|
||||
continue
|
||||
|
||||
// Filter by search query
|
||||
val text = (transferDetail.from ?: "").toLowerCase() + (transferDetail.to ?: "").toLowerCase()
|
||||
if (text.contains(mFilterOptions.query, ignoreCase = true)) {
|
||||
filteredTransfersDetails.add(transferDetail)
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces the list of TransferDetail items with the new filtered list
|
||||
transfersDetailsAdapter.replaceAll(filteredTransfersDetails)
|
||||
|
||||
if (scrollToTop)
|
||||
rvTransactions.scrollToPosition(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets called when the user selects some filter options in the [FilterOptionsDialog] and wants to apply them.
|
||||
*/
|
||||
override fun onFilterOptionsSelected(filterOptions: FilterOptions) {
|
||||
mFilterOptions = filterOptions
|
||||
applyFilterOptions(true)
|
||||
mViewModel.applyFilterOptions(filterOptions)
|
||||
}
|
||||
|
||||
/** Verifies that the storage permission has been granted before attempting to generate the export options */
|
||||
|
@ -267,12 +192,13 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
return
|
||||
}
|
||||
|
||||
mViewModel.getFilteredTransactionsOnce()?.let { filteredTransactions ->
|
||||
if (exportPDF)
|
||||
activity?.let { PDFGeneratorTask(it).execute(filteredTransfersDetails) }
|
||||
activity?.let { PDFGeneratorTask(it).execute(filteredTransactions) }
|
||||
|
||||
if (exportCSV)
|
||||
activity?.let { CSVGenerationTask(it).execute(filteredTransfersDetails) }
|
||||
|
||||
activity?.let { CSVGenerationTask(it).execute(filteredTransactions) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -1,15 +1,122 @@
|
|||
package cy.agorise.bitsybitshareswallet.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.*
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import cy.agorise.bitsybitshareswallet.models.FilterOptions
|
||||
import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var mRepository = TransferDetailRepository(application)
|
||||
|
||||
internal fun getAll(userId: String): LiveData<List<TransferDetail>> {
|
||||
return mRepository.getAll(userId)
|
||||
/**
|
||||
* [FilterOptions] used to filter the list of [TransferDetail] taken from the database
|
||||
*/
|
||||
private var mFilterOptions = FilterOptions()
|
||||
|
||||
private lateinit var transactions : LiveData<List<TransferDetail>>
|
||||
|
||||
/**
|
||||
* This [MediatorLiveData] is used to combine two sources of information into one, keeping the
|
||||
* client of this [ViewModel] receiving only one stream of data (a list of filtered [TransferDetail])
|
||||
*/
|
||||
private val filteredTransactions = MediatorLiveData<List<TransferDetail>>()
|
||||
|
||||
init {
|
||||
// Initialize the start and end dates for the FilterOptions
|
||||
val calendar = Calendar.getInstance()
|
||||
mFilterOptions.endDate = calendar.timeInMillis
|
||||
calendar.add(Calendar.MONTH, -2)
|
||||
mFilterOptions.startDate = calendar.timeInMillis
|
||||
}
|
||||
|
||||
internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
|
||||
transactions = mRepository.getAll(userId)
|
||||
|
||||
filteredTransactions.addSource(transactions) { transactions ->
|
||||
viewModelScope.launch {
|
||||
filteredTransactions.value = filter(transactions, mFilterOptions)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTransactions
|
||||
}
|
||||
|
||||
internal fun getFilterOptions(): FilterOptions {
|
||||
return mFilterOptions
|
||||
}
|
||||
|
||||
internal fun applyFilterOptions(filterOptions: FilterOptions) = transactions.value?.let { transactions ->
|
||||
viewModelScope.launch {
|
||||
filteredTransactions.value = filter(transactions, filterOptions)
|
||||
}
|
||||
}.also { mFilterOptions = filterOptions }
|
||||
|
||||
internal fun setFilterQuery(query: String) = transactions.value?.let { transactions ->
|
||||
mFilterOptions.query = query
|
||||
viewModelScope.launch {
|
||||
filteredTransactions.value = filter(transactions, mFilterOptions)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getFilteredTransactionsOnce() = filteredTransactions.value
|
||||
|
||||
/**
|
||||
* Filters the given list of [TransferDetail] given the [FilterOptions] and returns a filtered list
|
||||
* of [TransferDetail], doing all the work in a background thread using kotlin coroutines
|
||||
*/
|
||||
private suspend fun filter(transactions: List<TransferDetail>, filterOptions: FilterOptions) : List<TransferDetail> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
|
||||
// Create a list to store the filtered transactions
|
||||
val filteredTransactions = ArrayList<TransferDetail>()
|
||||
|
||||
// Make sure the filter dates use the same format as the transactions' dates
|
||||
val startDate = filterOptions.startDate / 1000
|
||||
val endDate = filterOptions.endDate / 1000
|
||||
|
||||
for (transaction in transactions) {
|
||||
// Filter by transfer direction
|
||||
if (transaction.direction) { // Transfer sent
|
||||
if (filterOptions.transactionsDirection == 1)
|
||||
// Looking for received transfers only
|
||||
continue
|
||||
} else { // Transfer received
|
||||
if (filterOptions.transactionsDirection == 2)
|
||||
// Looking for sent transactions only
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
if (!filterOptions.dateRangeAll && (transaction.date < startDate ||
|
||||
transaction.date > endDate))
|
||||
continue
|
||||
|
||||
// Filter by asset
|
||||
if (!filterOptions.assetAll && transaction.assetSymbol != filterOptions.asset)
|
||||
continue
|
||||
|
||||
// Filter by equivalent value
|
||||
if (!filterOptions.equivalentValueAll && ((transaction.fiatAmount ?: -1 ) < filterOptions.fromEquivalentValue
|
||||
|| (transaction.fiatAmount ?: -1) > filterOptions.toEquivalentValue))
|
||||
continue
|
||||
|
||||
// Filter transactions sent to agorise
|
||||
if (filterOptions.agoriseFees && transaction.to.equals("agorise"))
|
||||
continue
|
||||
|
||||
// Filter by search query
|
||||
val text = (transaction.from ?: "").toLowerCase() + (transaction.to ?: "").toLowerCase()
|
||||
if (text.contains(filterOptions.query, ignoreCase = true)) {
|
||||
filteredTransactions.add(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
filteredTransactions
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue