Merge branch 'develop' of github.com:Agorise/bitsy-wallet into develop

This commit is contained in:
Severiano Jaramillo 2019-11-06 21:36:57 -06:00
commit 0092f2b225
10 changed files with 129 additions and 28 deletions

View file

@ -4,6 +4,7 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -12,6 +13,7 @@ import android.preference.PreferenceManager
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
@ -47,6 +49,7 @@ import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap 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. * The app uses the single Activity methodology, but this activity was created so that MainActivity can extend from it.
@ -127,7 +130,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
// Configure ConnectedActivityViewModel to obtain missing equivalent values // Configure ConnectedActivityViewModel to obtain missing equivalent values
mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java) mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java)
mConnectedActivityViewModel.observeMissingEquivalentValuesIn("usd") //TODO: Obtain this from shared preferences? val currency = Currency.getInstance(Locale.getDefault())
mConnectedActivityViewModel.observeMissingEquivalentValuesIn(currency.currencyCode) //TODO: Obtain this from shared preferences?
// Configure UserAccountViewModel to obtain the missing account ids // Configure UserAccountViewModel to obtain the missing account ids
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
@ -174,6 +178,20 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
this.handleError(it) this.handleError(it)
}) })
mCompositeDisposable.add(disposable) 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()
}
}
} }
/** /**

View file

@ -13,4 +13,10 @@ interface EquivalentValueDao {
@Query("SELECT * FROM equivalent_values") @Query("SELECT * FROM equivalent_values")
fun getAllEquivalentValues(): LiveData<List<EquivalentValue>> fun getAllEquivalentValues(): LiveData<List<EquivalentValue>>
@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
} }

View file

@ -42,8 +42,8 @@ interface TransferDetailDao {
assets.precision AS `assetPrecision`, assets.precision AS `assetPrecision`,
assets.symbol AS `assetSymbol`, assets.symbol AS `assetSymbol`,
assets.issuer as `assetIssuer`, assets.issuer as `assetIssuer`,
(SELECT value FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id) AS `fiatAmount`, (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) AS `fiatSymbol` (SELECT symbol FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id AND symbol=:currency) AS `fiatSymbol`
FROM FROM
transfers transfers
INNER JOIN INNER JOIN
@ -51,5 +51,5 @@ interface TransferDetailDao {
WHERE WHERE
transfers.transfer_asset_id = assets.id transfers.transfer_asset_id = assets.id
""") """)
fun getAll(userId: String): LiveData<List<TransferDetail>> fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>>
} }

View file

@ -1,7 +1,7 @@
package cy.agorise.bitsybitshareswallet.network package cy.agorise.bitsybitshareswallet.network
import retrofit2.Call
import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice
import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
import retrofit2.http.Query import retrofit2.http.Query
@ -13,4 +13,8 @@ interface CoingeckoService {
fun getHistoricalValueSync(@Query("id") id: String, fun getHistoricalValueSync(@Query("id") id: String,
@Query("date") date: String, @Query("date") date: String,
@Query("localization") localization: Boolean): Call<HistoricalPrice> @Query("localization") localization: Boolean): Call<HistoricalPrice>
@Headers("Content-Type: application/json")
@GET("/api/v3/simple/supported_vs_currencies")
fun getSupportedCurrencies(): Call<Array<String>>
} }

View file

@ -15,4 +15,8 @@ class EquivalentValuesRepository(context: Context) {
mEquivalentValuesDao = db?.equivalentValueDao() mEquivalentValuesDao = db?.equivalentValueDao()
mTransfersDao = db?.transferDao() mTransfersDao = db?.transferDao()
} }
fun purge(): Int? {
return mEquivalentValuesDao?.purge()
}
} }

View file

@ -3,24 +3,27 @@ package cy.agorise.bitsybitshareswallet.repositories
import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase 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.TransferDetail
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetailDao import cy.agorise.bitsybitshareswallet.database.joins.TransferDetailDao
class TransferDetailRepository internal constructor(context: Context) { class TransferDetailRepository internal constructor(context: Context) {
private val mTransferDetailDao: TransferDetailDao private val mTransferDetailDao: TransferDetailDao
private val mEquivalentValuesDao: EquivalentValueDao
init { init {
val db = BitsyDatabase.getDatabase(context) val db = BitsyDatabase.getDatabase(context)
mTransferDetailDao = db!!.transferDetailDao() mTransferDetailDao = db!!.transferDetailDao()
mEquivalentValuesDao = db.equivalentValueDao()
} }
fun get(userId: String, transferId: String): LiveData<TransferDetail> { fun get(userId: String, transferId: String): LiveData<TransferDetail> {
return mTransferDetailDao.get(userId, transferId) return mTransferDetailDao.get(userId, transferId)
} }
fun getAll(userId: String): LiveData<List<TransferDetail>> { fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>> {
return mTransferDetailDao.getAll(userId) return mTransferDetailDao.getAll(userId, currency)
} }
} }

View file

@ -12,9 +12,9 @@ import cy.agorise.bitsybitshareswallet.database.entities.Transfer
import cy.agorise.bitsybitshareswallet.network.CoingeckoService import cy.agorise.bitsybitshareswallet.network.CoingeckoService
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -77,18 +77,16 @@ class TransferRepository internal constructor(context: Context) {
*/ */
fun observeMissingEquivalentValuesIn(symbol: String) { fun observeMissingEquivalentValuesIn(symbol: String) {
compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol) compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.map { transfer -> obtainFiatValue(transfer, symbol) } .map { transfer -> obtainFiatValue(transfer, symbol) }
.subscribe({ .subscribe({
Log.d(TAG,"Got equivalent value: $it") if(it.value >= 0) mEquivalentValuesDao.insert(it)
mEquivalentValuesDao.insert(it)
},{ },{
Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}") Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}")
for(element in it.stackTrace){ for(element in it.stackTrace){
Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}") Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}")
} }
})) })
)
} }
/** /**
@ -110,12 +108,14 @@ class TransferRepository internal constructor(context: Context) {
?.execute() ?.execute()
var equivalentFiatValue = -1L var equivalentFiatValue = -1L
if(response?.isSuccessful == true){ if(response?.isSuccessful == true){
val price: Double = response.body()?.market_data?.current_price?.get(symbol) ?: -1.0 val price: Double = response.body()?.market_data?.current_price?.get(symbol.toLowerCase()) ?: -1.0
// The equivalent value is obtained by: if(price > 0){
// 1- Dividing the base value by 100000 (BTS native precision) // The equivalent value is obtained by:
// 2- Multiplying that BTS value by the unit price in the chosen fiat // 1- Dividing the base value by 100000 (BTS native precision)
// 3- Multiplying the resulting value by 100 in order to express it in cents // 2- Multiplying that BTS value by the unit price in the chosen fiat
equivalentFiatValue = Math.round(transfer.btsValue?.div(1e5)?.times(price)?.times(100) ?: -1.0) // 3- Multiplying the resulting value by 100 in order to express it in cents
equivalentFiatValue = Math.round(transfer.btsValue?.toFloat()?.div(1e5)?.times(price)?.times(100) ?: -1.0)
}
}else{ }else{
Log.w(TAG,"Request was not successful. code: ${response?.code()}") Log.w(TAG,"Request was not successful. code: ${response?.code()}")
} }
@ -160,4 +160,27 @@ class TransferRepository internal constructor(context: Context) {
if(!compositeDisposable.isDisposed) if(!compositeDisposable.isDisposed)
compositeDisposable.clear() compositeDisposable.clear()
} }
/**
* Method used to override a given currency if it turns out not to be supported by the API.
* <p>
* 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<String> {
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
}
}
} }

View file

@ -128,7 +128,10 @@ object Constants {
/** Minimum time period in seconds between BitShares nodes list updates */ /** Minimum time period in seconds between BitShares nodes list updates */
const val NODES_UPDATE_PERIOD = (60 * 60).toLong() // 1 hour 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 /////////////////////// /////////////////////// Crashlytics custom keys ///////////////////////
/** Key used to add the last visited fragment name to the Crashlytics report */ /** Key used to add the last visited fragment name to the Crashlytics report */

View file

@ -1,15 +1,21 @@
package cy.agorise.bitsybitshareswallet.viewmodels package cy.agorise.bitsybitshareswallet.viewmodels
import android.app.Application import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
import cy.agorise.bitsybitshareswallet.repositories.EquivalentValuesRepository
import cy.agorise.bitsybitshareswallet.repositories.NodeRepository import cy.agorise.bitsybitshareswallet.repositories.NodeRepository
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
import cy.agorise.graphenej.network.FullNode import cy.agorise.graphenej.network.FullNode
import io.reactivex.schedulers.Schedulers
class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) { class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) {
companion object {
val TAG = "ConnectedActivityVM"
}
private var mTransfersRepository = TransferRepository(application) private var mTransfersRepository = TransferRepository(application)
private var mEquivalentValuesRep = EquivalentValuesRepository(application)
private val mNodeRepository: NodeRepository private val mNodeRepository: NodeRepository
init { init {
@ -18,7 +24,17 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
} }
fun observeMissingEquivalentValuesIn(symbol: String) { 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<FullNode>) { fun updateNodeLatencies(nodes: List<FullNode>) {
@ -29,4 +45,8 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
super.onCleared() super.onCleared()
mTransfersRepository.onCleared() mTransfersRepository.onCleared()
} }
fun purgeEquivalentValues(): Int? {
return mEquivalentValuesRep.purge()
}
} }

View file

@ -1,17 +1,25 @@
package cy.agorise.bitsybitshareswallet.viewmodels package cy.agorise.bitsybitshareswallet.viewmodels
import android.app.Application import android.app.Application
import android.util.Log
import androidx.lifecycle.* import androidx.lifecycle.*
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
import cy.agorise.bitsybitshareswallet.models.FilterOptions import cy.agorise.bitsybitshareswallet.models.FilterOptions
import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
class TransactionsViewModel(application: Application) : AndroidViewModel(application) { class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
companion object {
val TAG = "TransactionsViewModel"
}
private var mRepository = TransferDetailRepository(application) private var mRepository = TransferDetailRepository(application)
private var mTransfersRepository = TransferRepository(application)
/** /**
* [FilterOptions] used to filter the list of [TransferDetail] taken from the database * [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<List<TransferDetail>> { internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
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 -> filteredTransactions.addSource(transactions) { transactions ->
viewModelScope.launch { viewModelScope.launch {
filteredTransactions.value = filter(transactions, mFilterOptions) 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 return filteredTransactions
} }