Updated on 2026-08-14

This commit is contained in:
Tangem 2020-09-16 21:21:58 +03:00
parent 1966925a94
commit a98af7681e
22 changed files with 241 additions and 52 deletions

View file

@ -4,6 +4,7 @@ import android.app.Application
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.appReducer
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.wallet.BuildConfig
import org.rekotlin.Store
import timber.log.Timber
@ -13,10 +14,12 @@ val store = Store(
middleware = AppState.getMiddleware(),
state = AppState()
)
lateinit var preferencesStorage: PreferencesStorage
class TapApplication : Application() {
override fun onCreate() {
super.onCreate()
preferencesStorage = PreferencesStorage(this)
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())

View file

@ -5,6 +5,6 @@ package com.tangem.tap.common.entities
*/
class TapCurrency {
companion object{
val main = "USD"
const val DEFAULT_FIAT_CURRENCY = "USD"
}
}

View file

@ -6,6 +6,8 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
@ -46,10 +48,10 @@ fun BigDecimal.toFormattedString(decimals: Int): String {
return df.format(bd)
}
fun BigDecimal.toFiatString(rateValue: BigDecimal): String? {
fun BigDecimal.toFiatString(rateValue: BigDecimal, fiatCurrencyName: FiatCurrencyName): String? {
var fiatValue = rateValue.multiply(this)
fiatValue = fiatValue.setScale(2, RoundingMode.DOWN)
return "≈ USD $fiatValue"
return "≈ ${fiatCurrencyName} $fiatValue"
}
fun BigDecimal.stripZeroPlainString(): String = this.stripTrailingZeros().toPlainString()
@ -67,4 +69,6 @@ fun BigDecimal.isGreaterThanOrEqual(value: BigDecimal): Boolean {
fun BigDecimal.isLessThanOrEqual(value: BigDecimal): Boolean {
val compareResult = this.compareTo(value)
return compareResult == -1 || compareResult == 0
}
}
fun FiatCurrency.toFormattedString(): String = "${this.name} (${this.symbol}) - ${this.sign}"

View file

@ -7,5 +7,11 @@ import java.math.BigDecimal
sealed class GlobalAction : Action {
data class SaveScanNoteResponse(val scanNoteResponse: ScanNoteResponse) : GlobalAction()
data class SetFiatRate(val fiatRates: Pair<String, BigDecimal>) : GlobalAction()
data class SetFiatRate(
val fiatRates: Pair<CryptoCurrencyName, BigDecimal>
) : GlobalAction()
data class ChangeAppCurrency(val appCurrency: FiatCurrencyName) : GlobalAction()
object RestoreAppCurrency : GlobalAction() {
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.tap.common.redux.global
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
{ nextDispatch ->
{ action ->
when (action) {
is GlobalAction.RestoreAppCurrency -> {
store.dispatch(GlobalAction.RestoreAppCurrency.Success(
preferencesStorage.getAppCurrency()
))
}
}
nextDispatch(action)
}
}
}

View file

@ -13,9 +13,15 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.SaveScanNoteResponse ->
newState = newState.copy(scanNoteResponse = action.scanNoteResponse)
is GlobalAction.SetFiatRate -> {
val rates = newState.fiatRates.rates.toMutableMap()
val rates = newState.conversionRates.rates.toMutableMap()
rates[action.fiatRates.first] = action.fiatRates.second
newState = newState.copy(fiatRates = FiatRates(rates))
newState = newState.copy(conversionRates = ConversionRates(rates))
}
is GlobalAction.ChangeAppCurrency -> {
newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
}
is GlobalAction.RestoreAppCurrency.Success -> {
newState = newState.copy(appCurrency = action.appCurrency, conversionRates = ConversionRates(mapOf()))
}
}
return newState

View file

@ -1,22 +1,33 @@
package com.tangem.tap.common.redux.global
import com.tangem.commands.common.network.TangemService
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
import org.rekotlin.StateType
import java.math.BigDecimal
data class GlobalState(
val scanNoteResponse: ScanNoteResponse? = null,
val tapWalletManager: TapWalletManager = TapWalletManager(),
val fiatRates: FiatRates = FiatRates(emptyMap()),
val payIdManager: PayIdManager = PayIdManager(),
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
val tangemService: TangemService = TangemService(),
val conversionRates: ConversionRates = ConversionRates(emptyMap()),
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
) : StateType
data class FiatRates(
val rates: Map<String, BigDecimal>
data class ConversionRates(
val rates: Map<CryptoCurrencyName, BigDecimal>,
) {
fun getRateForCryptoCurrency(currency: String): BigDecimal? {
return rates[currency]
fun getRate(cryptoCurrency: CryptoCurrencyName): BigDecimal? {
return rates[cryptoCurrency]
}
}
typealias CryptoCurrencyName = String
typealias FiatCurrencyName = String

View file

@ -8,6 +8,8 @@ import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.TangemService
import com.tangem.common.extensions.toHexString
import com.tangem.tap.TapConfig
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.wallet.redux.PayIdState
@ -54,15 +56,15 @@ class TapWalletManager {
result?.let { handlePayIdResult(it) }
}
suspend fun loadFiatRate() {
suspend fun loadFiatRate(fiatCurrency: FiatCurrencyName) {
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
val blockchainCurrency = wallet?.blockchain?.currency
val tokenCurrency = wallet?.token?.symbol
val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it) }
val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it) }
val blockchainRate = blockchainCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) }
val tokenRate = tokenCurrency?.let { coinMarketCapService.getRate(it, fiatCurrency) }
val results = mutableListOf<Pair<String, Result<BigDecimal>?>>()
val results = mutableListOf<Pair<CryptoCurrencyName, Result<BigDecimal>?>>()
if (blockchainCurrency != null) results.add(blockchainCurrency to blockchainRate)
if (tokenCurrency != null) results.add(tokenCurrency to tokenRate)
@ -83,7 +85,7 @@ class TapWalletManager {
store.dispatch(WalletAction.LoadWallet)
store.dispatch(WalletAction.LoadFiatRate)
store.dispatch(WalletAction.LoadPayId)
} else if (data.card.status == CardStatus.Empty){
} else if (data.card.status == CardStatus.Empty) {
store.dispatch(WalletAction.EmptyWallet)
} else {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
@ -153,7 +155,7 @@ class TapWalletManager {
}
}
private suspend fun handleFiatRatesResult(results: List<Pair<String, Result<BigDecimal>?>>) {
private suspend fun handleFiatRatesResult(results: List<Pair<CryptoCurrencyName, Result<BigDecimal>?>>) {
withContext(Dispatchers.Main) {
results.map {
when (it.second) {

View file

@ -0,0 +1,45 @@
package com.tangem.tap.features.details.ui
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.network.coinmarketcap.FiatCurrency
import com.tangem.tap.store
import com.tangem.wallet.R
class CurrencySelectionDialog {
var dialog: AlertDialog? = null
fun show(currencies: List<FiatCurrency>, currentAppCurrency: FiatCurrencyName, context: Context) {
if (dialog == null) {
val currenciesToShow = currencies.map { it.toFormattedString() }.toTypedArray()
var currentSelection = currencies.indexOfFirst { it.symbol == currentAppCurrency }
dialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.details_currency))
.setNeutralButton(context.getString(R.string.generic_cancel)) { _, _ ->
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
}
.setPositiveButton(context.getString(R.string.generic_done)) { _, _ ->
val selectedCurrency = currencies[currentSelection]
store.dispatch(DetailsAction.AppCurrencyAction.SelectAppCurrency(selectedCurrency.symbol))
}
.setOnDismissListener {
store.dispatch(DetailsAction.AppCurrencyAction.Cancel)
}
.setSingleChoiceItems(currenciesToShow, currentSelection) { _, which ->
currentSelection = which
}.show()
}
}
fun clear() {
dialog = null
}
}

View file

@ -5,6 +5,7 @@ import android.net.Uri
import androidx.core.content.ContextCompat.startActivity
import com.tangem.common.CompletionResult
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.scope
@ -25,6 +26,7 @@ val homeMiddleware: Middleware<AppState> = { dispatch, state ->
withContext(Dispatchers.Main) {
when (result) {
is CompletionResult.Success -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.state.globalState.tapWalletManager.onCardScanned(result.data)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.extensions.isNegative
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.AmountAction
@ -13,6 +12,7 @@ import com.tangem.tap.features.send.redux.states.AmountState
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.Value
import com.tangem.tap.store
import java.math.BigDecimal
/**
@ -42,7 +42,7 @@ class AmountReducer : SendInternalReducer {
state.copy(
viewAmountValue = fiatToSend.stripZeroPlainString(),
viewBalanceValue = converter.toFiat(state.balanceCrypto).stripZeroPlainString(),
mainCurrency = Value(MainCurrencyType.FIAT, TapCurrency.main),
mainCurrency = Value(MainCurrencyType.FIAT, store.state.globalState.appCurrency),
maxLengthOfAmount = sendState.getDecimals(action.mainCurrency),
cursorAtTheSamePosition = false
)

View file

@ -3,11 +3,11 @@ package com.tangem.tap.features.send.redux.reducers
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Wallet
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
import com.tangem.tap.features.send.redux.SendScreenAction
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.store
/**
[REDACTED_AUTHOR]
@ -163,7 +163,7 @@ class ReceiptReducer : SendInternalReducer {
private fun determineSymbols(wallet: Wallet): ReceiptSymbols {
return ReceiptSymbols(
fiat = TapCurrency.main,
fiat = store.state.globalState.appCurrency,
crypto = wallet.blockchain.currency,
token = wallet.amounts[AmountType.Token]?.currencySymbol
)

View file

@ -65,7 +65,7 @@ private class PrepareSendScreenStatesReducer : SendInternalReducer {
}
private fun createCurrencyConverter(walletManager: WalletManager): CurrencyConverter {
val rate = store.state.globalState.fiatRates.getRateForCryptoCurrency(walletManager.wallet.blockchain.currency)
val rate = store.state.globalState.conversionRates.getRate(walletManager.wallet.blockchain.currency)
return if (rate == null) CurrencyConverter(BigDecimal.ONE) else CurrencyConverter(rate)
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.extensions.isZero
import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.common.entities.TapCurrency
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.store
import org.rekotlin.StateType
@ -60,7 +59,7 @@ enum class SendButtonState {
data class AmountState(
val viewAmountValue: String = BigDecimal.ZERO.toPlainString(),
val viewBalanceValue: String = BigDecimal.ZERO.toPlainString(),
val mainCurrency: Value<MainCurrencyType> = Value(MainCurrencyType.FIAT, TapCurrency.main),
val mainCurrency: Value<MainCurrencyType> = Value(MainCurrencyType.FIAT, store.state.globalState.appCurrency),
val typeOfAmount: AmountType = AmountType.Coin,
val amountToSendCrypto: BigDecimal = BigDecimal.ZERO,
val balanceCrypto: BigDecimal = BigDecimal.ZERO,

View file

@ -197,7 +197,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
private fun restoreMainCurrency(): MainCurrencyType {
val sp = requireContext().getSharedPreferences("SendScreen", Context.MODE_PRIVATE)
val mainCurrency = sp.getString("mainCurrency", TapCurrency.main)
val mainCurrency = sp.getString("mainCurrency", TapCurrency.DEFAULT_FIAT_CURRENCY)
val foundType = MainCurrencyType.values()
.firstOrNull { it.name.toLowerCase() == mainCurrency!!.toLowerCase() } ?: MainCurrencyType.FIAT
return foundType

View file

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.commands.Card
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.global.CryptoCurrencyName
import com.tangem.tap.domain.TapError
import com.tangem.wallet.R
import org.rekotlin.Action
@ -24,7 +25,7 @@ sealed class WalletAction : Action {
}
object LoadFiatRate : WalletAction() {
data class Success(val fiatRates: Pair<String, BigDecimal>) : WalletAction()
data class Success(val fiatRates: Pair<CryptoCurrencyName, BigDecimal?>) : WalletAction()
object Failure : WalletAction()
}

View file

@ -42,7 +42,7 @@ val walletMiddleware: Middleware<AppState> = { dispatch, state ->
}
is WalletAction.LoadFiatRate -> {
scope.launch {
store.state.globalState.tapWalletManager.loadFiatRate()
store.state.globalState.tapWalletManager.loadFiatRate(store.state.globalState.appCurrency)
}
}
is WalletAction.LoadArtwork -> {

View file

@ -86,10 +86,11 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
)
}
is WalletAction.LoadWallet.Success -> {
val fiatCurrencySymbol = state.globalState.appCurrency
val token = action.wallet.amounts[AmountType.Token]
val tokenData = if (token != null) {
val tokenFiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(token.currencySymbol)
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it) }
val tokenFiatRate = state.globalState.conversionRates.getRate(token.currencySymbol)
val tokenFiatAmount = tokenFiatRate?.let { token.value?.toFiatString(it, fiatCurrencySymbol) }
TokenData(
token.value?.toFormattedString(token.decimals) ?: "",
token.currencySymbol, tokenFiatAmount)
@ -97,8 +98,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
null
}
val amount = action.wallet.amounts[AmountType.Coin]?.value
val fiatRate = state.globalState.fiatRates.getRateForCryptoCurrency(action.wallet.blockchain.currency)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it) }
val fiatRate = state.globalState.conversionRates.getRate(action.wallet.blockchain.currency)
val fiatAmount = fiatRate?.let { amount?.toFiatString(it, fiatCurrencySymbol) }
val pendingTransactions = action.wallet.transactions
.toPendingTransactions(action.wallet.address)
@ -135,16 +136,24 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
errorMessage = action.errorMessage
)
)
is WalletAction.LoadFiatRate -> {
newState.copy(currencyData = newState.currencyData.copy(
fiatAmount = null,
token = newState.currencyData.token?.copy(fiatAmount = null))
)
}
is WalletAction.LoadFiatRate.Success -> {
val rate = action.fiatRates.second
val rate = action.fiatRates.second ?: return newState
val currency = action.fiatRates.first
val fiatAmount = if (currency == newState.wallet?.blockchain?.currency) {
newState.wallet?.amounts?.get(AmountType.Coin)?.value?.toFiatString(rate)
newState.wallet?.amounts?.get(AmountType.Coin)?.value
?.toFiatString(rate, state.globalState.appCurrency)
} else {
newState.currencyData.fiatAmount
}
val tokenFiatAmount = if (currency == newState.wallet?.token?.symbol) {
newState.wallet?.amounts?.get(AmountType.Token)?.value?.toFiatString(rate)
newState.wallet?.amounts?.get(AmountType.Token)?.value
?.toFiatString(rate, state.globalState.appCurrency)
} else {
newState.currencyData.token?.fiatAmount
}

View file

@ -12,9 +12,14 @@ interface CoinMarketCapApi {
@GET("v1/tools/price-conversion")
suspend fun getRateInfo(
@Query("amount") amount: Int,
@Query("symbol") cryptoId: String
@Query("symbol") cryptoCurrencyName: String,
@Query("convert") fiatCurrencyName: String? = null
): RateInfoResponse
@GET("v1/fiat/map")
suspend fun getFiatMap(): FiatMapResponse
companion object {
private const val baseUrl = "https://pro-api.coinmarketcap.com/"

View file

@ -2,6 +2,7 @@ package com.tangem.tap.network.coinmarketcap
import com.tangem.commands.common.network.Result
import com.tangem.commands.common.network.performRequest
import com.tangem.tap.common.redux.global.FiatCurrencyName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.math.BigDecimal
@ -9,10 +10,20 @@ import java.math.BigDecimal
class CoinMarketCapService {
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() }
suspend fun getRate(currency: String): Result<BigDecimal> = withContext(Dispatchers.IO) {
val response = performRequest { api.getRateInfo(1, currency) }
suspend fun getRate(
currency: String, fiatCurrency: FiatCurrencyName? = null
): Result<BigDecimal> = withContext(Dispatchers.IO) {
val response = performRequest { api.getRateInfo(1, currency, fiatCurrency) }
return@withContext when (response) {
is Result.Success -> Result.Success(response.data.data.quote.usd.price)
is Result.Success -> Result.Success(response.data.data.getRate())
is Result.Failure -> response
}
}
suspend fun getFiatCurrencies(): Result<List<FiatCurrency>> = withContext(Dispatchers.IO) {
val response = performRequest { api.getFiatMap() }
return@withContext when (response) {
is Result.Success -> Result.Success(response.data.data.sortedBy { it.name })
is Result.Failure -> response
}
}

View file

@ -5,21 +5,14 @@ import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class RateInfoResponse(
val status: Status,
val data: RateData
)
class RateInfoResponse : CoinMarketResponse<RateData>()
@JsonClass(generateAdapter = true)
data class RateData(
val quote: Quote
)
@JsonClass(generateAdapter = true)
data class Quote(
@Json(name = "USD")
val usd: CurrencyRate
)
val quote: Map<String, CurrencyRate>
) {
fun getRate(): BigDecimal = quote.values.first().price
}
@JsonClass(generateAdapter = true)
data class CurrencyRate(
@ -38,4 +31,21 @@ data class Status(
@Json(name = "credit_count")
val creditCount: Int,
val notice: String?
)
)
@JsonClass(generateAdapter = true)
class FiatMapResponse : CoinMarketResponse<List<FiatCurrency>>()
@JsonClass(generateAdapter = true)
open class CoinMarketResponse<T : Any> {
lateinit var status: Status
lateinit var data: T
}
@JsonClass(generateAdapter = true)
data class FiatCurrency(
val id: Int,
val name: String,
val sign: String,
val symbol: String
)

View file

@ -0,0 +1,54 @@
package com.tangem.tap.persistence
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY
import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.network.coinmarketcap.FiatCurrency
class PreferencesStorage(applicationContext: Application) {
private val preferences: SharedPreferences by lazy {
applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
}
private val fiatCurrenciesAdapter: JsonAdapter<List<FiatCurrency>> by lazy {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val type = Types.newParameterizedType(List::class.java, FiatCurrency::class.java)
moshi.adapter(type)
}
fun getAppCurrency(): FiatCurrencyName {
return preferences.getString(APP_CURRENCY_KEY, DEFAULT_FIAT_CURRENCY)
?: DEFAULT_FIAT_CURRENCY
}
fun saveAppCurrency(fiatCurrencyName: FiatCurrencyName) {
return preferences.edit().putString(APP_CURRENCY_KEY, fiatCurrencyName).apply()
}
fun getFiatCurrencies(): List<FiatCurrency>? {
val json = preferences.getString(FIAT_CURRENCIES_KEY, "")
return if (json.isNullOrBlank()) null else fiatCurrenciesAdapter.fromJson(json) as List<FiatCurrency>
}
fun saveFiatCurrencies(currencies: List<FiatCurrency>) {
val json: String = fiatCurrenciesAdapter.toJson(currencies)
return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply()
}
companion object {
private const val PREFERENCES_NAME = "tapPrefs"
private const val APP_CURRENCY_KEY = "appCurrency"
private const val FIAT_CURRENCIES_KEY = "fiatCurrencies"
}
}