Avoid crash due to unsupported currency locale.
- Avoided a crash in ConnectedActivity when trying to obtain the Locale's associated currency, when a Locale does not have a currency. - Standardized the process to obtain the coingecko supported currency, which will first try to use the current locale's currency and fallback to USD in case the first is not supported.
This commit is contained in:
parent
766d42386a
commit
9cabc0565a
7 changed files with 66 additions and 117 deletions
|
@ -19,6 +19,7 @@ import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
|
import cy.agorise.bitsybitshareswallet.utils.Helper
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceViewModel
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.ConnectedActivityViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.ConnectedActivityViewModel
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.TransferViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.TransferViewModel
|
||||||
|
@ -121,7 +122,9 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
||||||
mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java)
|
mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java)
|
||||||
|
|
||||||
val currency = Currency.getInstance(Locale.getDefault())
|
val currency = Currency.getInstance(Locale.getDefault())
|
||||||
mConnectedActivityViewModel.observeMissingEquivalentValuesIn(currency.currencyCode) //TODO: Obtain this from shared preferences?
|
val currencyCode = Helper.getCoingeckoSupportedCurrency(currency.currencyCode)
|
||||||
|
Log.d(TAG, "Using currency: ${currencyCode.toUpperCase(Locale.ROOT)}")
|
||||||
|
mConnectedActivityViewModel.observeMissingEquivalentValuesIn(currencyCode)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter
|
||||||
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
||||||
import cy.agorise.bitsybitshareswallet.models.FilterOptions
|
import cy.agorise.bitsybitshareswallet.models.FilterOptions
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
|
import cy.agorise.bitsybitshareswallet.utils.Helper
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
|
||||||
import cy.agorise.bitsybitshareswallet.views.DatePickerFragment
|
import cy.agorise.bitsybitshareswallet.views.DatePickerFragment
|
||||||
import kotlinx.android.synthetic.main.dialog_filter_options.*
|
import kotlinx.android.synthetic.main.dialog_filter_options.*
|
||||||
|
@ -158,9 +159,9 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
|
||||||
llEquivalentValue.visibility = if(isChecked) View.GONE else View.VISIBLE }
|
llEquivalentValue.visibility = if(isChecked) View.GONE else View.VISIBLE }
|
||||||
cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll
|
cbEquivalentValue.isChecked = mFilterOptions.equivalentValueAll
|
||||||
|
|
||||||
// TODO obtain user selected currency
|
val currency = Currency.getInstance(Locale.getDefault())
|
||||||
val currencySymbol = "usd"
|
val currencyCode = Helper.getCoingeckoSupportedCurrency(currency.currencyCode)
|
||||||
mCurrency = Currency.getInstance(currencySymbol)
|
mCurrency = Currency.getInstance(currencyCode)
|
||||||
|
|
||||||
val fromEquivalentValue = mFilterOptions.fromEquivalentValue /
|
val fromEquivalentValue = mFilterOptions.fromEquivalentValue /
|
||||||
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
|
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
|
||||||
|
@ -170,7 +171,7 @@ class FilterOptionsDialog : DialogFragment(), DatePickerFragment.OnDateSetListen
|
||||||
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
|
Math.pow(10.0, mCurrency.defaultFractionDigits.toDouble()).toLong()
|
||||||
etToEquivalentValue.setText("$toEquivalentValue", TextView.BufferType.EDITABLE)
|
etToEquivalentValue.setText("$toEquivalentValue", TextView.BufferType.EDITABLE)
|
||||||
|
|
||||||
tvEquivalentValueSymbol.text = currencySymbol.toUpperCase()
|
tvEquivalentValueSymbol.text = currencyCode.toUpperCase(Locale.getDefault())
|
||||||
|
|
||||||
// Initialize transaction network fees
|
// Initialize transaction network fees
|
||||||
switchAgoriseFees.isChecked = mFilterOptions.agoriseFees
|
switchAgoriseFees.isChecked = mFilterOptions.agoriseFees
|
||||||
|
|
|
@ -13,8 +13,4 @@ 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>>
|
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@ 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 java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -164,41 +163,4 @@ 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()
|
|
||||||
// Updating the supported currencies cache
|
|
||||||
mPreferences.edit()
|
|
||||||
.putStringSet(Constants.KEY_COINGECKO_CURRENCIES_CACHE, response?.body()?.toMutableSet() ?: setOf())
|
|
||||||
.apply()
|
|
||||||
if(response?.body()?.indexOf(symbol.toLowerCase()) == -1)
|
|
||||||
"usd"
|
|
||||||
else
|
|
||||||
it
|
|
||||||
}
|
|
||||||
.onErrorReturn {
|
|
||||||
// Error caused potentially by the lack of connectivity. If this happens we just
|
|
||||||
// retrieve the value from the cache
|
|
||||||
val currencies = mPreferences.getStringSet(Constants.KEY_COINGECKO_CURRENCIES_CACHE, setOf())
|
|
||||||
var selectedCurrency = "usd"
|
|
||||||
if(currencies.contains(symbol)) {
|
|
||||||
selectedCurrency = symbol
|
|
||||||
}
|
|
||||||
selectedCurrency
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -10,51 +10,64 @@ import androidx.core.content.FileProvider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains methods that are helpful in different parts of the app
|
* Contains methods that are helpful in different parts of the app
|
||||||
*/
|
*/
|
||||||
class Helper {
|
object Helper {
|
||||||
|
private const val TAG = "Helper"
|
||||||
|
|
||||||
companion object {
|
/**
|
||||||
private val TAG = "Helper"
|
* Creates and returns a Bitmap from the contents of a View, does not matter
|
||||||
|
* if it is a simple view or a ViewGroup like a ConstraintLayout or a LinearLayout.
|
||||||
|
*
|
||||||
|
* @param view The view that is gonna be pictured.
|
||||||
|
* @return The generated image from the given view.
|
||||||
|
*/
|
||||||
|
fun loadBitmapFromView(view: View): Bitmap {
|
||||||
|
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
view.draw(canvas)
|
||||||
|
|
||||||
/**
|
return bitmap
|
||||||
* Creates and returns a Bitmap from the contents of a View, does not matter
|
}
|
||||||
* if it is a simple view or a ViewGroup like a ConstraintLayout or a LinearLayout.
|
|
||||||
*
|
|
||||||
* @param view The view that is gonna be pictured.
|
|
||||||
* @return The generated image from the given view.
|
|
||||||
*/
|
|
||||||
fun loadBitmapFromView(view: View): Bitmap {
|
|
||||||
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
|
|
||||||
val canvas = Canvas(bitmap)
|
|
||||||
view.draw(canvas)
|
|
||||||
|
|
||||||
return bitmap
|
fun saveTemporalBitmap(context: Context, bitmap: Bitmap): Uri {
|
||||||
|
// save bitmap to cache directory
|
||||||
|
try {
|
||||||
|
val cachePath = File(context.cacheDir, "images")
|
||||||
|
if (!cachePath.mkdirs())
|
||||||
|
// don't forget to make the directory
|
||||||
|
Log.d(TAG, "shareBitmapImage creating cache images folder")
|
||||||
|
|
||||||
|
val stream = FileOutputStream("$cachePath/image.png") // overwrites this image every time
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
||||||
|
stream.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.d(TAG, "shareBitmapImage error: " + e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveTemporalBitmap(context: Context, bitmap: Bitmap): Uri {
|
// Send intent to share image+text
|
||||||
// save bitmap to cache directory
|
val imagePath = File(context.cacheDir, "images")
|
||||||
try {
|
val newFile = File(imagePath, "image.png")
|
||||||
val cachePath = File(context.cacheDir, "images")
|
|
||||||
if (!cachePath.mkdirs())
|
|
||||||
// don't forget to make the directory
|
|
||||||
Log.d(TAG, "shareBitmapImage creating cache images folder")
|
|
||||||
|
|
||||||
val stream = FileOutputStream(cachePath.toString() + "/image.png") // overwrites this image every time
|
// Create and return image uri
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
return FileProvider.getUriForFile(context, "cy.agorise.bitsybitshareswallet.FileProvider", newFile)
|
||||||
stream.close()
|
}
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(TAG, "shareBitmapImage error: " + e.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send intent to share image+text
|
/**
|
||||||
val imagePath = File(context.cacheDir, "images")
|
* If the given currency code is supported, returns it, else returns the default one.
|
||||||
val newFile = File(imagePath, "image.png")
|
*/
|
||||||
|
fun getCoingeckoSupportedCurrency(currencyCode: String): String {
|
||||||
|
val supportedCurrencies = setOf("usd", "aed", "ars", "aud", "bdt", "bhd", "bmd", "brl", "cad",
|
||||||
|
"chf", "clp", "cny", "czk", "dkk", "eur", "gbp", "hkd", "huf", "idr", "ils", "inr", "jpy",
|
||||||
|
"krw", "kwd", "lkr", "mmk", "mxn", "myr", "nok", "nzd", "php", "pkr", "pln", "rub", "sar",
|
||||||
|
"sek", "sgd", "thb", "try", "twd", "uah", "vef", "vnd", "zar", "xdr", "xag", "xau")
|
||||||
|
|
||||||
// Create and return image uri
|
return if (currencyCode.toLowerCase(Locale.ROOT) in supportedCurrencies)
|
||||||
return FileProvider.getUriForFile(context, "cy.agorise.bitsybitshareswallet.FileProvider", newFile)
|
currencyCode
|
||||||
}
|
else
|
||||||
|
"usd"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
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.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 {
|
companion object {
|
||||||
|
@ -24,17 +22,7 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
|
||||||
}
|
}
|
||||||
|
|
||||||
fun observeMissingEquivalentValuesIn(symbol: String) {
|
fun observeMissingEquivalentValuesIn(symbol: String) {
|
||||||
mTransfersRepository.getSupportedCurrency(symbol)
|
mTransfersRepository.observeMissingEquivalentValuesIn(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>) {
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
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 cy.agorise.bitsybitshareswallet.utils.Helper
|
||||||
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
|
||||||
|
@ -16,10 +13,9 @@ import java.util.*
|
||||||
|
|
||||||
class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
|
class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "TransactionsViewModel"
|
const 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
|
||||||
|
@ -44,24 +40,14 @@ class TransactionsViewModel(application: Application) : AndroidViewModel(applica
|
||||||
|
|
||||||
internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
|
internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
|
||||||
val currency = Currency.getInstance(Locale.getDefault())
|
val currency = Currency.getInstance(Locale.getDefault())
|
||||||
mTransfersRepository.getSupportedCurrency(currency.currencyCode)
|
val currencyCode = Helper.getCoingeckoSupportedCurrency(currency.currencyCode)
|
||||||
.subscribeOn(Schedulers.io())
|
transactions = mRepository.getAll(userId, currencyCode)
|
||||||
.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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue