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.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.
@ -127,7 +130,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
// Configure ConnectedActivityViewModel to obtain missing equivalent values
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
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
@ -174,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()
}
}
}
/**

View file

@ -13,4 +13,10 @@ interface EquivalentValueDao {
@Query("SELECT * FROM equivalent_values")
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.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<List<TransferDetail>>
fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>>
}

View file

@ -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<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()
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 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<TransferDetail> {
return mTransferDetailDao.get(userId, transferId)
}
fun getAll(userId: String): LiveData<List<TransferDetail>> {
return mTransferDetailDao.getAll(userId)
fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>> {
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.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")
mEquivalentValuesDao.insert(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,12 +108,14 @@ class TransferRepository internal constructor(context: Context) {
?.execute()
var equivalentFiatValue = -1L
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
if(price > 0){
// The equivalent value is obtained by:
// 1- Dividing the base value by 100000 (BTS native precision)
// 2- Multiplying that BTS value by the unit price in the chosen fiat
// 3- Multiplying the resulting value by 100 in order to express it in cents
equivalentFiatValue = Math.round(transfer.btsValue?.div(1e5)?.times(price)?.times(100) ?: -1.0)
equivalentFiatValue = Math.round(transfer.btsValue?.toFloat()?.div(1e5)?.times(price)?.times(100) ?: -1.0)
}
}else{
Log.w(TAG,"Request was not successful. code: ${response?.code()}")
}
@ -160,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.
* <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 */
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 */

View file

@ -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<FullNode>) {
@ -29,4 +45,8 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
super.onCleared()
mTransfersRepository.onCleared()
}
fun purgeEquivalentValues(): Int? {
return mEquivalentValuesRep.purge()
}
}

View file

@ -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<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 ->
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
}