Updated on 2026-08-14
This commit is contained in:
parent
8f5db97d2f
commit
801a02506f
19 changed files with 255 additions and 338 deletions
|
|
@ -16,7 +16,6 @@ import com.tangem.datasource.api.common.MoshiConverter
|
|||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -202,10 +201,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
// [REDACTED_JIRA]
|
||||
runBlocking {
|
||||
featureTogglesManager.init()
|
||||
initConfigManager(
|
||||
loader = FeaturesLocalLoader(assetLoader, BuildConfig.ENVIRONMENT),
|
||||
onComplete = ::initWithConfigDependency,
|
||||
)
|
||||
|
||||
val config = configManager.initialize()
|
||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||
|
||||
initWithConfigDependency(config = config)
|
||||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
|
|
@ -223,7 +223,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
||||
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
|
||||
walletConnect2Repository.init(projectId = configManager.getConfigSync().walletConnectProjectId)
|
||||
}
|
||||
|
||||
private fun createReduxStore(): Store<AppState> {
|
||||
|
|
@ -278,13 +278,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
System.loadLibrary("TrustWalletCore")
|
||||
}
|
||||
|
||||
private suspend fun initConfigManager(loader: FeaturesLocalLoader, onComplete: (Config) -> Unit) {
|
||||
configManager.load(loader) { config ->
|
||||
store.dispatch(GlobalAction.SetConfigManager(configManager))
|
||||
onComplete(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWithConfigDependency(config: Config) {
|
||||
initAnalytics(this, config)
|
||||
Log.addLogger(logger = tangemSdkLogger)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
is GlobalAction.ExchangeManager.Init -> {
|
||||
val appStateSafe = appState() ?: return
|
||||
val config = appStateSafe.globalState.configManager?.config ?: return
|
||||
val config = appStateSafe.globalState.configManager?.getConfigSync() ?: return
|
||||
|
||||
scope.launch {
|
||||
val scanResponseProvider: () -> ScanResponse? = {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ internal class DefaultStakeKitAuthProvider(
|
|||
) : StakeKitAuthProvider {
|
||||
|
||||
override fun getApiKey(): String {
|
||||
return configManager.config.stakeKitApiKey ?: error("No StakeKit api key provided")
|
||||
return configManager.getConfigSync().stakeKitApiKey ?: error("No StakeKit api key provided")
|
||||
}
|
||||
}
|
||||
|
|
@ -55,9 +55,9 @@ internal class Express(
|
|||
|
||||
private fun getApiKey(isProd: Boolean): String {
|
||||
return if (isProd) {
|
||||
configManager.config.express
|
||||
configManager.getConfigSync().express
|
||||
} else {
|
||||
configManager.config.devExpress
|
||||
configManager.getConfigSync().devExpress
|
||||
}
|
||||
?.apiKey
|
||||
?: error("No express config provided")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
package com.tangem.datasource.config
|
||||
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Config manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ConfigManager {
|
||||
|
||||
val config: Config
|
||||
/** Initialize and return [Config] */
|
||||
suspend fun initialize(): Config
|
||||
|
||||
suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)? = null)
|
||||
/** Get [Config] as [Flow] */
|
||||
fun getConfig(): Flow<Config>
|
||||
|
||||
/** Get [Config] synchronously */
|
||||
fun getConfigSync(): Config
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package com.tangem.datasource.config
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
||||
|
||||
override var config: Config = Config()
|
||||
private set
|
||||
|
||||
override suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)?) {
|
||||
configLoader.load { configModel ->
|
||||
setupConfigValues(configModel.configValues)
|
||||
onComplete?.invoke(config)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupConfigValues(configValues: ConfigValueModel?) {
|
||||
val values = configValues ?: return
|
||||
|
||||
config = createConfig(config, values)
|
||||
}
|
||||
|
||||
private fun createConfig(config: Config, configValues: ConfigValueModel): Config {
|
||||
return config.copy(
|
||||
moonPayApiKey = configValues.moonPayApiKey,
|
||||
moonPayApiSecretKey = configValues.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = configValues.mercuryoWidgetId,
|
||||
mercuryoSecret = configValues.mercuryoSecret,
|
||||
blockchainSdkConfig = BlockchainSdkConfig(
|
||||
blockchairCredentials = BlockchairCredentials(
|
||||
apiKey = configValues.blockchairApiKeys,
|
||||
authToken = configValues.blockchairAuthorizationToken,
|
||||
),
|
||||
blockcypherTokens = configValues.blockcypherTokens,
|
||||
quickNodeSolanaCredentials = QuickNodeCredentials(
|
||||
apiKey = configValues.quiknodeApiKey,
|
||||
subdomain = configValues.quiknodeSubdomain,
|
||||
),
|
||||
quickNodeBscCredentials = QuickNodeCredentials(
|
||||
apiKey = configValues.bscQuiknodeApiKey,
|
||||
subdomain = configValues.bscQuiknodeSubdomain,
|
||||
),
|
||||
infuraProjectId = configValues.infuraProjectId,
|
||||
tronGridApiKey = configValues.tronGridApiKey,
|
||||
nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey),
|
||||
getBlockCredentials = createGetBlockCredentials(configValues),
|
||||
kaspaSecondaryApiUrl = configValues.kaspaSecondaryApiUrl,
|
||||
tonCenterCredentials = TonCenterCredentials(
|
||||
mainnetApiKey = configValues.tonCenterKeys.mainnet,
|
||||
testnetApiKey = configValues.tonCenterKeys.testnet,
|
||||
),
|
||||
chiaFireAcademyApiKey = configValues.chiaFireAcademyApiKey,
|
||||
chiaTangemApiKey = configValues.chiaTangemApiKey,
|
||||
hederaArkhiaApiKey = configValues.hederaArkhiaKey,
|
||||
polygonScanApiKey = configValues.polygonScanApiKey,
|
||||
bittensorDwellirApiKey = configValues.bittensorDwellirApiKey,
|
||||
bittensorOnfinalityApiKey = configValues.bittensorOnfinalityKey,
|
||||
koinosProApiKey = configValues.koinosProApiKey,
|
||||
),
|
||||
amplitudeApiKey = configValues.amplitudeApiKey,
|
||||
walletConnectProjectId = configValues.walletConnectProjectId,
|
||||
express = configValues.express,
|
||||
devExpress = configValues.devExpress,
|
||||
stakeKitApiKey = configValues.stakeKitApiKey,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? {
|
||||
return configValues.getBlockAccessTokens?.let { accessTokens ->
|
||||
GetBlockCredentials(
|
||||
xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC),
|
||||
cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta),
|
||||
avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC),
|
||||
eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC),
|
||||
etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC),
|
||||
fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC),
|
||||
rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC),
|
||||
bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC),
|
||||
polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC),
|
||||
gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC),
|
||||
cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC),
|
||||
solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC),
|
||||
ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC),
|
||||
tron = GetBlockAccessToken(rest = accessTokens.tron?.rest),
|
||||
cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest),
|
||||
near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC),
|
||||
aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest),
|
||||
dogecoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.dogecoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.dogecoin?.blockBookRest,
|
||||
),
|
||||
litecoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.litecoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.litecoin?.blockBookRest,
|
||||
),
|
||||
dash = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.dash?.jsonRPC,
|
||||
blockBookRest = accessTokens.dash?.blockBookRest,
|
||||
),
|
||||
bitcoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.bitcoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.bitcoin?.blockBookRest,
|
||||
),
|
||||
algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest),
|
||||
zkSyncEra = GetBlockAccessToken(rest = accessTokens.zksync?.jsonRPC),
|
||||
polygonZkEvm = GetBlockAccessToken(rest = accessTokens.polygonZkevm?.jsonRPC),
|
||||
base = GetBlockAccessToken(rest = accessTokens.base?.jsonRPC),
|
||||
blast = GetBlockAccessToken(jsonRpc = accessTokens.blast?.jsonRPC),
|
||||
filecoin = GetBlockAccessToken(jsonRpc = accessTokens.filecoin?.jsonRPC),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.datasource.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.converter.EnvironmentConfigConverter
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default config manager
|
||||
*
|
||||
* @property assetLoader asset loader
|
||||
* @property configStore config store
|
||||
*/
|
||||
internal class DefaultConfigManager(
|
||||
private val assetLoader: AssetLoader,
|
||||
private val configStore: RuntimeStateStore<Config>,
|
||||
) : ConfigManager {
|
||||
|
||||
override suspend fun initialize(): Config {
|
||||
val configValueModel = assetLoader.load<ConfigValueModel>(fileName = CONFIG_FILE_NAME)
|
||||
?: return configStore.get().value
|
||||
|
||||
val config = EnvironmentConfigConverter.convert(value = configValueModel)
|
||||
configStore.store(value = config)
|
||||
|
||||
Timber.i("Config [$CONFIG_FILE_NAME] loaded successfully")
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
override fun getConfig(): Flow<Config> = configStore.get()
|
||||
|
||||
override fun getConfigSync(): Config = configStore.get().value
|
||||
|
||||
private companion object {
|
||||
const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.datasource.config
|
||||
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class FeaturesLocalLoader(
|
||||
private val assetLoader: AssetLoader,
|
||||
buildEnvironment: String,
|
||||
) : Loader<ConfigModel> {
|
||||
|
||||
private val configValuesName = "tangem-app-config/config_$buildEnvironment"
|
||||
|
||||
override suspend fun load(onComplete: (ConfigModel) -> Unit) {
|
||||
ConfigModel(
|
||||
configValues = assetLoader.load<ConfigValueModel>(fileName = configValuesName),
|
||||
).also(onComplete)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.datasource.config
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Loader<T> {
|
||||
suspend fun load(onComplete: (T) -> Unit)
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.tangem.datasource.config.converter
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converts [ConfigValueModel] to [BlockchainSdkConfig]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object BlockchainSDKConfigConverter : Converter<ConfigValueModel, BlockchainSdkConfig> {
|
||||
|
||||
override fun convert(value: ConfigValueModel): BlockchainSdkConfig {
|
||||
return BlockchainSdkConfig(
|
||||
blockchairCredentials = BlockchairCredentials(
|
||||
apiKey = value.blockchairApiKeys,
|
||||
authToken = value.blockchairAuthorizationToken,
|
||||
),
|
||||
blockcypherTokens = value.blockcypherTokens,
|
||||
quickNodeSolanaCredentials = QuickNodeCredentials(
|
||||
apiKey = value.quiknodeApiKey,
|
||||
subdomain = value.quiknodeSubdomain,
|
||||
),
|
||||
quickNodeBscCredentials = QuickNodeCredentials(
|
||||
apiKey = value.bscQuiknodeApiKey,
|
||||
subdomain = value.bscQuiknodeSubdomain,
|
||||
),
|
||||
infuraProjectId = value.infuraProjectId,
|
||||
tronGridApiKey = value.tronGridApiKey,
|
||||
nowNodeCredentials = NowNodeCredentials(value.nowNodesApiKey),
|
||||
getBlockCredentials = createGetBlockCredentials(value),
|
||||
kaspaSecondaryApiUrl = value.kaspaSecondaryApiUrl,
|
||||
tonCenterCredentials = TonCenterCredentials(
|
||||
mainnetApiKey = value.tonCenterKeys.mainnet,
|
||||
testnetApiKey = value.tonCenterKeys.testnet,
|
||||
),
|
||||
chiaFireAcademyApiKey = value.chiaFireAcademyApiKey,
|
||||
chiaTangemApiKey = value.chiaTangemApiKey,
|
||||
hederaArkhiaApiKey = value.hederaArkhiaKey,
|
||||
polygonScanApiKey = value.polygonScanApiKey,
|
||||
bittensorDwellirApiKey = value.bittensorDwellirApiKey,
|
||||
bittensorOnfinalityApiKey = value.bittensorOnfinalityKey,
|
||||
koinosProApiKey = value.koinosProApiKey,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? {
|
||||
return configValues.getBlockAccessTokens?.let { accessTokens ->
|
||||
GetBlockCredentials(
|
||||
xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC),
|
||||
cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta),
|
||||
avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC),
|
||||
eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC),
|
||||
etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC),
|
||||
fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC),
|
||||
rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC),
|
||||
bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC),
|
||||
polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC),
|
||||
gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC),
|
||||
cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC),
|
||||
solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC),
|
||||
ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC),
|
||||
tron = GetBlockAccessToken(rest = accessTokens.tron?.rest),
|
||||
cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest),
|
||||
near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC),
|
||||
aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest),
|
||||
dogecoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.dogecoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.dogecoin?.blockBookRest,
|
||||
),
|
||||
litecoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.litecoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.litecoin?.blockBookRest,
|
||||
),
|
||||
dash = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.dash?.jsonRPC,
|
||||
blockBookRest = accessTokens.dash?.blockBookRest,
|
||||
),
|
||||
bitcoin = GetBlockAccessToken(
|
||||
jsonRpc = accessTokens.bitcoin?.jsonRPC,
|
||||
blockBookRest = accessTokens.bitcoin?.blockBookRest,
|
||||
),
|
||||
algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest),
|
||||
zkSyncEra = GetBlockAccessToken(jsonRpc = accessTokens.zksync?.jsonRPC),
|
||||
polygonZkEvm = GetBlockAccessToken(jsonRpc = accessTokens.polygonZkevm?.jsonRPC),
|
||||
base = GetBlockAccessToken(jsonRpc = accessTokens.base?.jsonRPC),
|
||||
blast = GetBlockAccessToken(jsonRpc = accessTokens.blast?.jsonRPC),
|
||||
filecoin = GetBlockAccessToken(jsonRpc = accessTokens.filecoin?.jsonRPC),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.datasource.config.converter
|
||||
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigValueModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [ConfigValueModel] to [Config]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object EnvironmentConfigConverter : Converter<ConfigValueModel, Config> {
|
||||
|
||||
override fun convert(value: ConfigValueModel): Config {
|
||||
return Config(
|
||||
moonPayApiKey = value.moonPayApiKey,
|
||||
moonPayApiSecretKey = value.moonPayApiSecretKey,
|
||||
mercuryoWidgetId = value.mercuryoWidgetId,
|
||||
mercuryoSecret = value.mercuryoSecret,
|
||||
blockchainSdkConfig = BlockchainSDKConfigConverter.convert(value = value),
|
||||
amplitudeApiKey = value.amplitudeApiKey,
|
||||
walletConnectProjectId = value.walletConnectProjectId,
|
||||
express = value.express,
|
||||
devExpress = value.devExpress,
|
||||
stakeKitApiKey = value.stakeKitApiKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -74,12 +74,4 @@ data class GetBlockToken(
|
|||
@Json(name = "blockBookRest") val blockBookRest: String?,
|
||||
@Json(name = "rest") val rest: String?,
|
||||
@Json(name = "rosetta") val rosetta: String?,
|
||||
)
|
||||
|
||||
class ConfigModel(
|
||||
val configValues: ConfigValueModel?,
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): ConfigModel = ConfigModel(null)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -24,8 +24,8 @@ internal class Sha256SignatureVerifier(
|
|||
private fun getPubKey(): String? {
|
||||
val expressConfig = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.Express)
|
||||
return when (expressConfig.environment) {
|
||||
ApiEnvironment.PROD -> configManager.config.express?.signVerifierPublicKey
|
||||
else -> configManager.config.devExpress?.signVerifierPublicKey
|
||||
ApiEnvironment.PROD -> configManager.getConfigSync().express?.signVerifierPublicKey
|
||||
else -> configManager.getConfigSync().devExpress?.signVerifierPublicKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,26 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.ConfigManagerImpl
|
||||
import dagger.Binds
|
||||
import com.tangem.datasource.config.DefaultConfigManager
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ConfigModule {
|
||||
internal object ConfigModule {
|
||||
|
||||
@Binds
|
||||
fun bindConfigManager(configManager: ConfigManagerImpl): ConfigManager
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesConfigManager(assetLoader: AssetLoader): ConfigManager {
|
||||
return DefaultConfigManager(
|
||||
assetLoader = assetLoader,
|
||||
configStore = RuntimeStateStore(defaultValue = Config()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.utils.Trigger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
|
||||
@Deprecated("Use shared preferences data store instead")
|
||||
internal class FileDataStore<Value : Any>(
|
||||
private val fileReader: FileReader,
|
||||
private val adapter: JsonAdapter<Value>,
|
||||
) : StringKeyDataStore<Value> {
|
||||
|
||||
private val mutex = Mutex()
|
||||
private val writeTrigger = Trigger()
|
||||
override suspend fun isEmpty(): Boolean {
|
||||
val e = NotImplementedError("`isEmpty()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean = getSyncOrNull(key) != null
|
||||
|
||||
override fun get(key: String): Flow<Value> {
|
||||
return writeTrigger
|
||||
.map {
|
||||
mutex.withLock {
|
||||
getInternal(key)
|
||||
}
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
override fun getAll(): Flow<List<Value>> {
|
||||
val e = NotImplementedError("`getAll()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: String): Value? {
|
||||
return getInternal(key)
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(): List<Value>? {
|
||||
val e = NotImplementedError("`getAllSyncOrNull()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: Value) {
|
||||
try {
|
||||
mutex.withLock {
|
||||
val json = adapter.toJson(value)
|
||||
|
||||
fileReader.rewriteFile(json, key)
|
||||
writeTrigger.trigger()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to write file: $key")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun store(values: Map<String, Value>) {
|
||||
values.forEach { (key, item) ->
|
||||
store(key, item)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
fileReader.removeFile(key)
|
||||
writeTrigger.trigger()
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: Collection<String>) {
|
||||
val e = NotImplementedError("`remove(keys)` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
val e = NotImplementedError("`clear()` function not implemented for `FileDataStore`")
|
||||
Timber.e(e)
|
||||
|
||||
throw e
|
||||
}
|
||||
|
||||
private fun getInternal(fileName: String): Value? {
|
||||
return try {
|
||||
val json = fileReader.readFile(fileName)
|
||||
|
||||
adapter.fromJson(json)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Unable to read file: $fileName")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.datasource.local.datastore
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Runtime store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface RuntimeStateStore<T> {
|
||||
|
||||
/** Get flow of elements [T] */
|
||||
fun get(): StateFlow<T>
|
||||
|
||||
/** Store [value] */
|
||||
suspend fun store(value: T)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create [RuntimeStateStore]
|
||||
*
|
||||
* @param T type of stored value
|
||||
* @param defaultValue default value
|
||||
*/
|
||||
operator fun <T> invoke(defaultValue: T): RuntimeStateStore<T> = object : RuntimeStateStore<T> {
|
||||
private val flow = MutableStateFlow(value = defaultValue)
|
||||
|
||||
override fun get(): StateFlow<T> = flow
|
||||
|
||||
override suspend fun store(value: T) {
|
||||
flow.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package com.tangem.datasource.local.datastore.utils
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* Represents a trigger mechanism to emit values on-demand.
|
||||
*
|
||||
* This class provides a mechanism to trigger emissions via the [trigger] method.
|
||||
*
|
||||
* @property triggerFlow The internal flow that gets toggled to trigger emissions.
|
||||
*/
|
||||
internal class Trigger(
|
||||
private val triggerFlow: MutableStateFlow<Boolean> = MutableStateFlow(value = false),
|
||||
) : Flow<Unit> {
|
||||
|
||||
/**
|
||||
* Collects values emitted by this flow.
|
||||
*
|
||||
* Overrides the default collection mechanism to emit a [Unit] value whenever [triggerFlow] changes.
|
||||
*
|
||||
* @param collector The collector responsible for handling emitted values.
|
||||
*/
|
||||
override suspend fun collect(collector: FlowCollector<Unit>): Nothing {
|
||||
triggerFlow.collect { collector.emit(Unit) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an emission.
|
||||
*/
|
||||
fun trigger() {
|
||||
triggerFlow.value = !triggerFlow.value
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
package com.tangem.datasource.api.common.config.managers
|
||||
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.Loader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.config.models.ConfigModel
|
||||
import com.tangem.datasource.config.models.ExpressModel
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* Mock [ConfigManager] implementation for [ProdApiConfigsManagerTest]
|
||||
|
|
@ -13,10 +12,17 @@ import com.tangem.datasource.config.models.ExpressModel
|
|||
*/
|
||||
internal class MockConfigManager : ConfigManager {
|
||||
|
||||
override val config = Config(
|
||||
express = ExpressModel(apiKey = ProdApiConfigsManagerTest.EXPRESS_API_KEY, signVerifierPublicKey = ""),
|
||||
devExpress = ExpressModel(apiKey = ProdApiConfigsManagerTest.EXPRESS_DEV_API_KEY, signVerifierPublicKey = ""),
|
||||
private val config = Config(
|
||||
express = ExpressModel(apiKey = EXPRESS_API_KEY, signVerifierPublicKey = "vocibus"),
|
||||
devExpress = ExpressModel(apiKey = EXPRESS_DEV_API_KEY, signVerifierPublicKey = "pellentesque"),
|
||||
)
|
||||
|
||||
override suspend fun load(configLoader: Loader<ConfigModel>, onComplete: ((config: Config) -> Unit)?) = Unit
|
||||
override suspend fun initialize() = config
|
||||
override fun getConfig() = flowOf(config)
|
||||
override fun getConfigSync() = config
|
||||
|
||||
companion object {
|
||||
const val EXPRESS_API_KEY = "express_api_key"
|
||||
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
|
||||
}
|
||||
}
|
||||
|
|
@ -65,8 +65,6 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
const val VERSION_NAME = "debug"
|
||||
const val EXPRESS_USER_ID = "express_user_id"
|
||||
const val EXPRESS_SESSION_ID = "express_session_id"
|
||||
const val EXPRESS_API_KEY = "express_api_key"
|
||||
const val EXPRESS_DEV_API_KEY = "express_dev_api_key"
|
||||
const val STAKE_KIT_API_KEY = "stake_kit_api_key"
|
||||
|
||||
@JvmStatic
|
||||
|
|
@ -107,7 +105,11 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
},
|
||||
headers = mapOf(
|
||||
"api-key" to Provider {
|
||||
if (environment == ApiEnvironment.PROD) EXPRESS_API_KEY else EXPRESS_DEV_API_KEY
|
||||
if (environment == ApiEnvironment.PROD) {
|
||||
MockConfigManager.EXPRESS_API_KEY
|
||||
} else {
|
||||
MockConfigManager.EXPRESS_DEV_API_KEY
|
||||
}
|
||||
},
|
||||
"user-id" to Provider { EXPRESS_USER_ID },
|
||||
"session-id" to Provider { EXPRESS_SESSION_ID },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue