Updated on 2026-08-14
This commit is contained in:
parent
5968cf338f
commit
369ea273c2
13 changed files with 143 additions and 21 deletions
|
|
@ -1,10 +1,16 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfig
|
||||
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
|
||||
import com.tangem.tap.common.images.PicassoHelper
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.appReducer
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.config.*
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.network.createMoshi
|
||||
import com.tangem.tap.persistence.PreferencesStorage
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import org.rekotlin.Store
|
||||
|
|
@ -20,11 +26,33 @@ lateinit var preferencesStorage: PreferencesStorage
|
|||
class TapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
|
||||
this.minimumFetchIntervalInSeconds = 60
|
||||
})
|
||||
} else {
|
||||
Firebase.remoteConfig.setConfigSettingsAsync(remoteConfigSettings {
|
||||
this.minimumFetchIntervalInSeconds = 3600
|
||||
})
|
||||
}
|
||||
|
||||
NetworkConnectivity.createInstance(store, this)
|
||||
preferencesStorage = PreferencesStorage(this)
|
||||
PicassoHelper.initPicassoWithCaching(this)
|
||||
|
||||
loadConfigs()
|
||||
}
|
||||
|
||||
|
||||
private fun loadConfigs() {
|
||||
val moshiAdapter = createMoshi().adapter(ConfigModel::class.java)
|
||||
val nameResolver = ConfigNameResolver.get()
|
||||
|
||||
val localLoader = LocalLoader(this, nameResolver, moshiAdapter)
|
||||
val remoteLoader = RemoteLoader(nameResolver, moshiAdapter)
|
||||
val configManager = ConfigManager(localLoader, remoteLoader)
|
||||
configManager.load { store.dispatch(GlobalAction.SetConfigManager(configManager)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.domain.config.ConfigManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -15,4 +16,5 @@ sealed class GlobalAction : Action {
|
|||
data class Success(val appCurrency: FiatCurrencyName) : GlobalAction()
|
||||
}
|
||||
data class UpdateWalletSignedHashes(val walletSignedHashes: Int?) : GlobalAction()
|
||||
data class SetConfigManager(val configManager: ConfigManager) : GlobalAction()
|
||||
}
|
||||
|
|
@ -33,6 +33,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
globalState
|
||||
}
|
||||
}
|
||||
is GlobalAction.SetConfigManager -> {
|
||||
globalState.copy(configManager = action.configManager)
|
||||
}
|
||||
else -> globalState
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ 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.config.ConfigManager
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.network.coinmarketcap.CoinMarketCapService
|
||||
import org.rekotlin.StateType
|
||||
|
|
@ -16,6 +17,7 @@ data class GlobalState(
|
|||
val coinMarketCapService: CoinMarketCapService = CoinMarketCapService(),
|
||||
val tangemService: TangemService = TangemService(),
|
||||
val conversionRates: ConversionRates = ConversionRates(emptyMap()),
|
||||
val configManager: ConfigManager? = null,
|
||||
val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.common.redux.global.CryptoCurrencyName
|
||||
import org.spongycastle.util.encoders.Base64
|
||||
import javax.crypto.Mac
|
||||
|
|
@ -19,12 +18,12 @@ class TopUpHelper {
|
|||
private const val REDIRECT_URL_PATH = "&redirectUrl="
|
||||
private const val SIGNATURE_PATH = "&signature="
|
||||
|
||||
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String): String {
|
||||
val originalQuery = API_KEY_PATH + TapConfig.moonPayApiKey.urlEncode() +
|
||||
fun getUrl(cryptoCurrencyName: CryptoCurrencyName, walletAddress: String, apiKey: String, secretKey: String): String {
|
||||
val originalQuery = API_KEY_PATH + apiKey.urlEncode() +
|
||||
CURRENCY_PATH + cryptoCurrencyName.urlEncode() +
|
||||
WALLET_ADDRESS_PATH + walletAddress.urlEncode() +
|
||||
REDIRECT_URL_PATH + REDIRECT_URL.urlEncode()
|
||||
val signature = createSignature(originalQuery, TapConfig.moonPayApiSecretKey)
|
||||
val signature = createSignature(originalQuery, secretKey)
|
||||
|
||||
return BASE_URL + originalQuery + SIGNATURE_PATH + signature.urlEncode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import timber.log.Timber
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ConfigLoader {
|
||||
suspend fun loadConfig(onComplete: (Config) -> Unit)
|
||||
fun loadConfig(onComplete: (Config) -> Unit)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class LocalLoader(
|
|||
private val adapter: JsonAdapter<ConfigModel>
|
||||
) : ConfigLoader {
|
||||
|
||||
override suspend fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
override fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
val config = try {
|
||||
val jsonFeatures = readAssetAsString(nameResolver.getFeaturesName())
|
||||
var configModel = adapter.fromJson(jsonFeatures)
|
||||
|
|
@ -74,7 +74,7 @@ class RemoteLoader(
|
|||
private val adapter: JsonAdapter<ConfigModel>
|
||||
) : ConfigLoader {
|
||||
|
||||
override suspend fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
override fun loadConfig(onComplete: (Config) -> Unit) {
|
||||
val emptyConfig = Config.empty()
|
||||
val remoteConfig = Firebase.remoteConfig
|
||||
remoteConfig.fetchAndActivate().addOnCompleteListener {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.tap.domain.config
|
||||
|
||||
import com.tangem.tangem_sdk_new.ui.animation.VoidCallback
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
enum class ConfigType {
|
||||
Local, Remote
|
||||
}
|
||||
|
||||
class Config(
|
||||
val features: MutableMap<String, Feature>,
|
||||
val configValues: MutableMap<String, ConfigurationValue>
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): Config = Config(mutableMapOf(), mutableMapOf())
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigManager(
|
||||
private val localLoader: ConfigLoader,
|
||||
private val remoteLoader: ConfigLoader
|
||||
) {
|
||||
private var localConfig: Config = Config.empty()
|
||||
private var remoteConfig: Config = Config.empty()
|
||||
|
||||
fun load(onComplete: VoidCallback? = null) {
|
||||
localLoader.loadConfig { localConfig = it }
|
||||
remoteLoader.loadConfig {
|
||||
remoteConfig = it
|
||||
onComplete?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun featureIsActive(name: String): Boolean {
|
||||
return getFeatureWithHighestPriority(name)?.isActive() ?: false
|
||||
}
|
||||
|
||||
fun getFeature(name: String, type: ConfigType? = null): Feature? {
|
||||
return when (type) {
|
||||
ConfigType.Local -> localConfig.features[name]
|
||||
ConfigType.Remote -> remoteConfig.features[name]
|
||||
else -> getFeatureWithHighestPriority(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFeatureWithHighestPriority(name: String): Feature? {
|
||||
// if a localFeature doesn't exist then a remoteFeature won't exist to
|
||||
val localFeature = localConfig.features[name] ?: return null
|
||||
|
||||
// if the localFeature has a condition, then it has higher priority than a remoteFeature
|
||||
return if (localFeature.hasCondition()) {
|
||||
localFeature
|
||||
} else {
|
||||
// if not then the remoteFeature has higher priority than the localFeature
|
||||
// if the remoteFeature doesn't exist - used the localFeature
|
||||
remoteConfig.features[name] ?: localFeature
|
||||
}
|
||||
}
|
||||
|
||||
fun getConfigValue(name: String): ConfigurationValue? {
|
||||
return remoteConfig.configValues[name] ?: localConfig.configValues[name]
|
||||
}
|
||||
|
||||
fun updateCondition(name: String, condition: Condition?) {
|
||||
localConfig.features[name]?.updateCondition(condition)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class FeatureModel(
|
|||
|
||||
class ConfigValueModel(
|
||||
override val name: String,
|
||||
override val value: String?
|
||||
override val value: String
|
||||
) : BaseConfigModel<String>
|
||||
|
||||
class ConfigModel(val features: List<FeatureModel>?, val configValues: List<ConfigValueModel>?)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ interface Feature {
|
|||
fun hasCondition(): Boolean
|
||||
|
||||
companion object {
|
||||
val payIdIsEnabled = "payIdIsEnabled"
|
||||
const val usePayId = "usePayId"
|
||||
const val payIdIsEnabled = "payIdIsEnabled"
|
||||
const val useTopUp = "useTopUp"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,4 +44,10 @@ class AppFeature(
|
|||
}
|
||||
}
|
||||
|
||||
data class ConfigurationValue(val name: String, val value: String?)
|
||||
data class ConfigurationValue(val name: String, val value: String) {
|
||||
companion object {
|
||||
const val coinMarketCapKey = "coinMarketCapKey"
|
||||
const val moonPayApiKey = "moonPayApiKey"
|
||||
const val moonPayApiSecretKey = "moonPayApiSecretKey"
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
|
|||
import com.tangem.tap.domain.PayIdManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.TopUpHelper
|
||||
import com.tangem.tap.domain.config.ConfigurationValue
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.features.send.redux.PrepareSendScreen
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
|
|
@ -253,9 +254,14 @@ private class TopUpMiddleware {
|
|||
fun handle(action: WalletAction.TopUpAction) {
|
||||
when (action) {
|
||||
is WalletAction.TopUpAction.TopUp -> {
|
||||
val configManager = store.state.globalState.configManager ?: return
|
||||
val apiKey = configManager.getConfigValue(ConfigurationValue.moonPayApiKey)?.value ?: ""
|
||||
val secretKey = configManager.getConfigValue(ConfigurationValue.moonPayApiSecretKey)?.value ?: ""
|
||||
val url = TopUpHelper.getUrl(
|
||||
store.state.walletState.currencyData.currencySymbol!!,
|
||||
store.state.walletState.addressData!!.address
|
||||
store.state.walletState.addressData!!.address,
|
||||
apiKey,
|
||||
secretKey
|
||||
)
|
||||
Timber.d(url)
|
||||
store.dispatch(WalletAction.TopUpAction.Start(url, TopUpHelper.REDIRECT_URL))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.network.coinmarketcap
|
||||
|
||||
import com.tangem.tap.TapConfig
|
||||
import com.tangem.tap.network.createRetrofitInstance
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
|
@ -23,20 +22,20 @@ interface CoinMarketCapApi {
|
|||
companion object {
|
||||
private const val baseUrl = "https://pro-api.coinmarketcap.com/"
|
||||
|
||||
fun create(): CoinMarketCapApi {
|
||||
fun create(apiKey: String): CoinMarketCapApi {
|
||||
return createRetrofitInstance(
|
||||
baseUrl,
|
||||
listOf(createCoinMarketRequestInterceptor()),
|
||||
listOf(createCoinMarketRequestInterceptor(apiKey)),
|
||||
).create(CoinMarketCapApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCoinMarketRequestInterceptor(): Interceptor {
|
||||
private fun createCoinMarketRequestInterceptor(apiKey: String): Interceptor {
|
||||
return object : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val requestBuilder = chain.request().newBuilder()
|
||||
requestBuilder.addHeader("X-CMC_PRO_API_KEY", TapConfig.coinMarketCapKey)
|
||||
requestBuilder.addHeader("X-CMC_PRO_API_KEY", apiKey)
|
||||
return chain.proceed(requestBuilder.build())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@ 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 com.tangem.tap.domain.config.ConfigurationValue
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
class CoinMarketCapService {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create() }
|
||||
class CoinMarketCapService() {
|
||||
private val api: CoinMarketCapApi by lazy { CoinMarketCapApi.create(getApiKey()) }
|
||||
|
||||
private fun getApiKey(): String {
|
||||
return store.state.globalState.configManager?.getConfigValue(ConfigurationValue.coinMarketCapKey)?.value!!
|
||||
}
|
||||
|
||||
suspend fun getRate(
|
||||
currency: String, fiatCurrency: FiatCurrencyName? = null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue