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

174 lines
6.6 KiB
Kotlin

package cy.agorise.bitsybitshareswallet.ui.transactions
import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.*
import com.google.android.material.datepicker.MaterialDatePicker
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
import cy.agorise.bitsybitshareswallet.domain.usecase.ExportTransactionsToCsvUseCase
import cy.agorise.bitsybitshareswallet.domain.usecase.ExportTransactionsToPdfUseCase
import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository
import cy.agorise.bitsybitshareswallet.utils.Helper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
// TODO Inject below dependencies
private val exportTransactionsToPdfUseCase = ExportTransactionsToPdfUseCase(application)
private val exportTransactionsToCsvUseCase = ExportTransactionsToCsvUseCase(application)
private val mRepository = TransferDetailRepository(application)
/**
* [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 = getClearedUtc()
calendar.timeInMillis = MaterialDatePicker.todayInUtcMilliseconds()
mFilterOptions.endDate = calendar.timeInMillis
calendar.roll(Calendar.MONTH, -2)
mFilterOptions.startDate = calendar.timeInMillis
}
private fun getClearedUtc(): Calendar {
val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
utc.clear()
return utc
}
internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault())
transactions = mRepository.getAll(userId, currencyCode)
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)
}
}
/**
* 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> = 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 ?: ""} ${transaction.to ?: ""} ${transaction.memo}"
if (text.contains(filterOptions.query, ignoreCase = true)) {
filteredTransactions.add(transaction)
}
}
filteredTransactions
}
/** Creates the export procedures for PDF and CSV, depending on the user selection. */
fun exportFilteredTransactions(folderUri: Uri, exportPdf: Boolean, exportCsv: Boolean) {
Log.d(TAG, "Export options selected. PDF: $exportPdf, CSV: $exportCsv")
if (!exportPdf && !exportCsv) return
// TODO Use injected context
val folderDocumentFile = DocumentFile.fromTreeUri(getApplication(), folderUri) ?: return
val transactions = filteredTransactions.value ?: return
if (exportPdf) {
viewModelScope.launch {
// TODO Show success/failure message
exportTransactionsToPdfUseCase(transactions, folderDocumentFile)
}
}
if (exportCsv) {
viewModelScope.launch {
// TODO Show success/failure message
exportTransactionsToCsvUseCase(transactions, folderDocumentFile)
}
}
}
companion object {
private const val TAG = "TransactionsViewModel"
}
}