diff --git a/app/src/main/assets/dev_features.json b/app/src/main/assets/dev_features.json index aa3fd4c598..ded87444cb 100644 --- a/app/src/main/assets/dev_features.json +++ b/app/src/main/assets/dev_features.json @@ -1,12 +1,12 @@ { "features": [ { - "name": "payIdCreationEnabled", - "value": false + "name": "payIdIsEnabled", + "value": true }, { "name": "usePayId", - "value": false + "value": true }, { "name": "showWelcomeMessageAtStart", diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b19bfc730e..24b84bb0d6 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index c0e641bffe..9fb73bdfcb 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 66fbb80140..2b27751177 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -33,6 +33,9 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState } } + is GlobalAction.SetConfigManager -> { + globalState.copy(configManager = action.configManager) + } else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 9da6906d9c..f9e05cdeac 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt b/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt index 6f715de06b..67d07b20d0 100644 --- a/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/TopUpHelper.kt @@ -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() } diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt b/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt index a9d07377cd..3cc7d2aec7 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt +++ b/app/src/main/java/com/tangem/tap/domain/config/ConfigLoader.kt @@ -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 ) : 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 ) : 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 { diff --git a/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt new file mode 100644 index 0000000000..f064353cc6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/config/ConfigManager.kt @@ -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, + val configValues: MutableMap +) { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt index 18e1e80766..7b70001f1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/config/JsonModels.kt @@ -16,7 +16,7 @@ class FeatureModel( class ConfigValueModel( override val name: String, - override val value: String? + override val value: String ) : BaseConfigModel class ConfigModel(val features: List?, val configValues: List?) diff --git a/app/src/main/java/com/tangem/tap/domain/config/Models.kt b/app/src/main/java/com/tangem/tap/domain/config/Models.kt index 07a3bf28bb..8f40b8d367 100644 --- a/app/src/main/java/com/tangem/tap/domain/config/Models.kt +++ b/app/src/main/java/com/tangem/tap/domain/config/Models.kt @@ -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?) \ No newline at end of file +data class ConfigurationValue(val name: String, val value: String) { + companion object { + const val coinMarketCapKey = "coinMarketCapKey" + const val moonPayApiKey = "moonPayApiKey" + const val moonPayApiSecretKey = "moonPayApiSecretKey" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index 71d07e315d..1f0b4769ee 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -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)) diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt index 47889ff06d..5d78846280 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapApi.kt @@ -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()) } } diff --git a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt index 4dc7931623..b31b784929 100644 --- a/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt +++ b/app/src/main/java/com/tangem/tap/network/coinmarketcap/CoinMarketCapService.kt @@ -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