From 8f70b3965d72726109df0be71e456c812f48a5cd Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Wed, 6 Nov 2019 16:53:39 -0500 Subject: [PATCH] Fixing wrong database entries & making the equivalent value currency dynamic - Purging invalid entries of the 'equivalent_values' table - Displaying whathever currency the user happen to have selected *at the moment*. The previous implementation had a flaw that would have made the currency that was used to calculate the equivalent values in first stick, even if the user changed to another locale settings later. --- .../activities/ConnectedActivity.kt | 17 ++++++++ .../database/daos/EquivalentValueDao.kt | 6 +++ .../database/joins/TransferDetailDao.kt | 6 +-- .../network/CoingeckoService.kt | 6 ++- .../EquivalentValuesRepository.kt | 4 ++ .../repositories/TransferDetailRepository.kt | 7 +++- .../repositories/TransferRepository.kt | 39 +++++++++++++------ .../bitsybitshareswallet/utils/Constants.kt | 5 ++- .../viewmodels/ConnectedActivityViewModel.kt | 24 +++++++++++- .../viewmodels/TransactionsViewModel.kt | 32 ++++++++++++--- 10 files changed, 119 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt index b167b32..817fe39 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt @@ -4,6 +4,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.content.pm.PackageManager import android.os.AsyncTask import android.os.Bundle import android.os.Handler @@ -12,6 +13,7 @@ import android.preference.PreferenceManager import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.pm.PackageInfoCompat import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import com.crashlytics.android.Crashlytics @@ -47,6 +49,7 @@ import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.ArrayList import kotlin.collections.HashMap +import kotlin.concurrent.thread /** * The app uses the single Activity methodology, but this activity was created so that MainActivity can extend from it. @@ -175,6 +178,20 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { this.handleError(it) }) mCompositeDisposable.add(disposable) + + thread { + val info = this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES) + val versionCode = PackageInfoCompat.getLongVersionCode(info) + val hasPurgedEquivalentValues = PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, false) + if(versionCode > 11 && !hasPurgedEquivalentValues) { + mConnectedActivityViewModel.purgeEquivalentValues() + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, true) + .apply() + } + } } /** diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt index 314c18a..38e00f5 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt @@ -13,4 +13,10 @@ interface EquivalentValueDao { @Query("SELECT * FROM equivalent_values") fun getAllEquivalentValues(): LiveData> + + @Query("SELECT COUNT(*) FROM equivalent_values WHERE symbol=:currency") + fun getEquivalentValuesCountForCurrency(currency: String): Long + + @Query("DELETE FROM equivalent_values WHERE value < 0") + fun purge(): Int } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt index 8fc17a5..d6021bc 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt @@ -42,8 +42,8 @@ interface TransferDetailDao { assets.precision AS `assetPrecision`, assets.symbol AS `assetSymbol`, assets.issuer as `assetIssuer`, - (SELECT value FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id) AS `fiatAmount`, - (SELECT symbol FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id) AS `fiatSymbol` + (SELECT value FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id AND symbol=:currency) AS `fiatAmount`, + (SELECT symbol FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id AND symbol=:currency) AS `fiatSymbol` FROM transfers INNER JOIN @@ -51,5 +51,5 @@ interface TransferDetailDao { WHERE transfers.transfer_asset_id = assets.id """) - fun getAll(userId: String): LiveData> + fun getAll(userId: String, currency: String): LiveData> } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt index 0d951ec..c25415d 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt @@ -1,7 +1,7 @@ package cy.agorise.bitsybitshareswallet.network -import retrofit2.Call import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice +import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.Query @@ -13,4 +13,8 @@ interface CoingeckoService { fun getHistoricalValueSync(@Query("id") id: String, @Query("date") date: String, @Query("localization") localization: Boolean): Call + + @Headers("Content-Type: application/json") + @GET("/api/v3/simple/supported_vs_currencies") + fun getSupportedCurrencies(): Call> } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt index dbedc1d..c5b6a40 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt @@ -15,4 +15,8 @@ class EquivalentValuesRepository(context: Context) { mEquivalentValuesDao = db?.equivalentValueDao() mTransfersDao = db?.transferDao() } + + fun purge(): Int? { + return mEquivalentValuesDao?.purge() + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferDetailRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferDetailRepository.kt index 91466da..3a6abd5 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferDetailRepository.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferDetailRepository.kt @@ -3,24 +3,27 @@ package cy.agorise.bitsybitshareswallet.repositories import android.content.Context import androidx.lifecycle.LiveData import cy.agorise.bitsybitshareswallet.database.BitsyDatabase +import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.database.joins.TransferDetailDao class TransferDetailRepository internal constructor(context: Context) { private val mTransferDetailDao: TransferDetailDao + private val mEquivalentValuesDao: EquivalentValueDao init { val db = BitsyDatabase.getDatabase(context) mTransferDetailDao = db!!.transferDetailDao() + mEquivalentValuesDao = db.equivalentValueDao() } fun get(userId: String, transferId: String): LiveData { return mTransferDetailDao.get(userId, transferId) } - fun getAll(userId: String): LiveData> { - return mTransferDetailDao.getAll(userId) + fun getAll(userId: String, currency: String): LiveData> { + return mTransferDetailDao.getAll(userId, currency) } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt index 45501b2..fbcc61c 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt @@ -12,9 +12,9 @@ import cy.agorise.bitsybitshareswallet.database.entities.Transfer import cy.agorise.bitsybitshareswallet.network.CoingeckoService import cy.agorise.bitsybitshareswallet.network.ServiceGenerator import cy.agorise.bitsybitshareswallet.utils.Constants +import io.reactivex.Observable import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable -import io.reactivex.schedulers.Schedulers import java.text.SimpleDateFormat import java.util.* @@ -77,18 +77,16 @@ class TransferRepository internal constructor(context: Context) { */ fun observeMissingEquivalentValuesIn(symbol: String) { compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) .map { transfer -> obtainFiatValue(transfer, symbol) } .subscribe({ - Log.d(TAG,"Got equivalent value: $it") if(it.value >= 0) mEquivalentValuesDao.insert(it) },{ Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}") for(element in it.stackTrace){ Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}") } - })) + }) + ) } /** @@ -110,13 +108,7 @@ class TransferRepository internal constructor(context: Context) { ?.execute() var equivalentFiatValue = -1L if(response?.isSuccessful == true){ - // We try to use the locale-selected currency first - var selectedCurrency = symbol - // Checking that the provided currency is supported by the Coingecko API - val isCurrencySupported = response?.body()?.market_data?.current_price?.keys?.contains(symbol.toLowerCase()) - // In case it is not, we fallback to USD - if(isCurrencySupported == false) selectedCurrency = "usd" - val price: Double = response.body()?.market_data?.current_price?.get(selectedCurrency.toLowerCase()) ?: -1.0 + val price: Double = response.body()?.market_data?.current_price?.get(symbol.toLowerCase()) ?: -1.0 if(price > 0){ // The equivalent value is obtained by: // 1- Dividing the base value by 100000 (BTS native precision) @@ -168,4 +160,27 @@ class TransferRepository internal constructor(context: Context) { if(!compositeDisposable.isDisposed) compositeDisposable.clear() } + + /** + * Method used to override a given currency if it turns out not to be supported by the API. + *

+ * The CoinGecko API supports 20+ fiat currencies. So we can very easily calculate historical + * equivalent values for those currencies. If the currency is not supported though, + * we must fall back to USD. + * + * @param symbol The 3 letters symbol of the currency + */ + fun getSupportedCurrency(symbol: String): Observable { + return Observable.just(symbol) + .map { + val sg = ServiceGenerator(Constants.COINGECKO_URL) + val response = sg.getService(CoingeckoService::class.java) + ?.getSupportedCurrencies() + ?.execute() + if(response?.body()?.indexOf(symbol.toLowerCase()) == -1) + "usd" + else + it + } + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt index 3d80977..1b052bb 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt @@ -128,7 +128,10 @@ object Constants { /** Minimum time period in seconds between BitShares nodes list updates */ const val NODES_UPDATE_PERIOD = (60 * 60).toLong() // 1 hour - + /** Because of a bug in pre-version code 11 releases, some entries in the equivalent values + * table were invalid. We'll be performing a purge at version code 12, but we only want to do + * it once. After this, we record this in shared preferences using this key */ + const val KEY_HAS_PURGED_EQUIVALENT_VALUES = "key_has_purged_equivalent_values" /////////////////////// Crashlytics custom keys /////////////////////// /** Key used to add the last visited fragment name to the Crashlytics report */ diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt index d4eb1b3..5a4dd75 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt @@ -1,15 +1,21 @@ package cy.agorise.bitsybitshareswallet.viewmodels import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel import cy.agorise.bitsybitshareswallet.database.BitsyDatabase +import cy.agorise.bitsybitshareswallet.repositories.EquivalentValuesRepository import cy.agorise.bitsybitshareswallet.repositories.NodeRepository import cy.agorise.bitsybitshareswallet.repositories.TransferRepository import cy.agorise.graphenej.network.FullNode +import io.reactivex.schedulers.Schedulers class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) { + companion object { + val TAG = "ConnectedActivityVM" + } private var mTransfersRepository = TransferRepository(application) - + private var mEquivalentValuesRep = EquivalentValuesRepository(application) private val mNodeRepository: NodeRepository init { @@ -18,7 +24,17 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap } fun observeMissingEquivalentValuesIn(symbol: String) { - mTransfersRepository.observeMissingEquivalentValuesIn(symbol) + mTransfersRepository.getSupportedCurrency(symbol) + .observeOn(Schedulers.io()) + .subscribeOn(Schedulers.io()) + .subscribe({ + currency -> mTransfersRepository.observeMissingEquivalentValuesIn(currency) + },{ + Log.e(TAG,"Error while trying to subscribe to missing equivalent values observer. Msg: ${it.message}") + for(element in it.stackTrace){ + Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}") + } + }) } fun updateNodeLatencies(nodes: List) { @@ -29,4 +45,8 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap super.onCleared() mTransfersRepository.onCleared() } + + fun purgeEquivalentValues(): Int? { + return mEquivalentValuesRep.purge() + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt index 68ad7bc..ad63f9d 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransactionsViewModel.kt @@ -1,17 +1,25 @@ package cy.agorise.bitsybitshareswallet.viewmodels import android.app.Application +import android.util.Log import androidx.lifecycle.* import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository +import cy.agorise.bitsybitshareswallet.repositories.TransferRepository +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.* class TransactionsViewModel(application: Application) : AndroidViewModel(application) { + companion object { + val TAG = "TransactionsViewModel" + } private var mRepository = TransferDetailRepository(application) + private var mTransfersRepository = TransferRepository(application) /** * [FilterOptions] used to filter the list of [TransferDetail] taken from the database @@ -35,13 +43,25 @@ class TransactionsViewModel(application: Application) : AndroidViewModel(applica } internal fun getFilteredTransactions(userId: String): LiveData> { - transactions = mRepository.getAll(userId) + val currency = Currency.getInstance(Locale.getDefault()) + mTransfersRepository.getSupportedCurrency(currency.currencyCode) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + Log.d(TAG,"Looking for currency with code: ${it}") + transactions = mRepository.getAll(userId, it) - filteredTransactions.addSource(transactions) { transactions -> - viewModelScope.launch { - filteredTransactions.value = filter(transactions, mFilterOptions) - } - } + filteredTransactions.addSource(transactions) { transactions -> + viewModelScope.launch { + filteredTransactions.value = filter(transactions, mFilterOptions) + } + } + },{ + Log.e(TAG,"Error while trying to obtain a filtered list of transactions. Msg: ${it.message}") + for(element in it.stackTrace){ + Log.e(ConnectedActivityViewModel.TAG,"${element.className}#${element.methodName}:${element.lineNumber}") + } + }) return filteredTransactions }