Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-28 19:28:50 +04:00
parent cd0b30eaec
commit 06876aeb1a
11 changed files with 98 additions and 92 deletions

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.datasource.api.utils.ReadTimeout
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.models.ProviderModel
import retrofit2.http.*
import java.util.concurrent.TimeUnit

View file

@ -8,7 +8,7 @@ import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.models.ProviderModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View file

@ -4,6 +4,8 @@ import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.DefaultEnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.EnvironmentConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
import com.tangem.datasource.local.config.providers.DefaultBlockchainProvidersStorage
import com.tangem.datasource.local.config.testnet.DefaultTestnetTokensStorage
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.datastore.RuntimeStateStore
@ -31,4 +33,13 @@ internal object ConfigModule {
fun providesTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
return DefaultTestnetTokensStorage(assetLoader)
}
@Provides
@Singleton
fun provideProvidersOrderConfigStorage(assetLoader: AssetLoader): BlockchainProvidersStorage {
return DefaultBlockchainProvidersStorage(
assetLoader = assetLoader,
runtimeStateStore = RuntimeStateStore(defaultValue = emptyMap()),
)
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.datasource.local.config.providers
import com.tangem.datasource.local.config.providers.models.ProviderModel
/**
* Blockchain providers storage
*
[REDACTED_AUTHOR]
*/
interface BlockchainProvidersStorage {
/** Get config */
suspend fun getConfigSync(): Map<String, List<ProviderModel>>
}

View file

@ -0,0 +1,33 @@
package com.tangem.datasource.local.config.providers
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.datasource.local.datastore.RuntimeStateStore
/**
* Default blockchain providers storage
*
* @property assetLoader asset loader
* @property runtimeStateStore runtime state store
*/
internal class DefaultBlockchainProvidersStorage(
private val assetLoader: AssetLoader,
private val runtimeStateStore: RuntimeStateStore<Map<String, List<ProviderModel>>>,
) : BlockchainProvidersStorage {
override suspend fun getConfigSync(): Map<String, List<ProviderModel>> {
val cachedData = runtimeStateStore.get().value
if (cachedData.isNotEmpty()) return cachedData
val config = assetLoader.load<Map<String, List<ProviderModel>>>(fileName = PROVIDER_TYPES_FILE_NAME).orEmpty()
runtimeStateStore.store(value = config)
return config
}
private companion object {
const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order"
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.datasource.local.config.environment.models
package com.tangem.datasource.local.config.providers.models
import com.squareup.moshi.Json

View file

@ -19,9 +19,9 @@ import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.FeedbackManager
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
@ -82,7 +82,7 @@ internal class SwapViewModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val feedbackManager: FeedbackManager,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
swapInteractorFactory: SwapInteractor.Factory,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
@ -1259,7 +1259,7 @@ internal class SwapViewModel @Inject constructor(
txId = dataState.swapDataModel?.transaction?.txId.orEmpty(),
)
feedbackManager.sendEmail(email)
sendFeedbackEmailUseCase(email)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter
import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader
import com.tangem.blockchainsdk.store.RuntimeStore
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope

View file

@ -6,7 +6,7 @@ import com.tangem.blockchainsdk.BlockchainProviderTypes
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.blockchainsdk.utils.createPrivateProviderType
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.utils.converter.Converter
import timber.log.Timber

View file

@ -5,8 +5,8 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.blockchainsdk.utils.createPrivateProviderType
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber
@ -16,16 +16,16 @@ import javax.inject.Singleton
/**
* Loader of [BlockchainProvidersResponse]
*
* @property tangemTechServiceApi tangem tech api
* @property assetLoader asset loader for local config loading
* @property dispatchers dispatchers
* @property tangemTechServiceApi tangem tech api
* @property blockchainProvidersStorage blockchain providers storage
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class BlockchainProvidersResponseLoader @Inject constructor(
private val tangemTechServiceApi: TangemTechApi,
private val assetLoader: AssetLoader,
private val blockchainProvidersStorage: BlockchainProvidersStorage,
private val dispatchers: CoroutineDispatcherProvider,
) {
@ -33,7 +33,7 @@ internal class BlockchainProvidersResponseLoader @Inject constructor(
/** Load [BlockchainProvidersResponse] */
suspend fun load(): BlockchainProvidersResponse? {
val localResponse = loadLocal() ?: return null
val localResponse = loadLocal().ifEmpty { return null }
return runCatching(dispatcher = dispatchers.io, block = ::loadRemote)
.fold(
@ -45,9 +45,7 @@ internal class BlockchainProvidersResponseLoader @Inject constructor(
)
}
private suspend fun loadLocal(): BlockchainProvidersResponse? {
return assetLoader.load<BlockchainProvidersResponse>(fileName = PROVIDER_TYPES_FILE_NAME)
}
private suspend fun loadLocal(): BlockchainProvidersResponse = blockchainProvidersStorage.getConfigSync()
private suspend fun loadRemote() = tangemTechServiceApi.getBlockchainProviders()
@ -140,8 +138,6 @@ internal class BlockchainProvidersResponseLoader @Inject constructor(
}
private companion object {
const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order"
val forbiddenSchemes = listOf("wss://")
}
}

View file

@ -1,16 +1,11 @@
package com.tangem.blockchainsdk.loader
import android.annotation.SuppressLint
import com.google.common.truth.Truth
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.asset.reader.AssetReader
import com.tangem.datasource.local.config.environment.models.ProviderModel
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.test.runTest
@ -20,21 +15,14 @@ import org.junit.Test
/**
[REDACTED_AUTHOR]
*/
@SuppressLint("CheckResult")
@OptIn(ExperimentalStdlibApi::class)
internal class BlockchainProvidersResponseLoaderTest {
private val tangemTechServiceApi = mockk<TangemTechApi>()
private val assetReader = mockk<AssetReader>()
private val moshi = mockk<Moshi>()
private val jsonAdapter = mockk<JsonAdapter<BlockchainProvidersResponse>>()
// Impossible to mockk AssetLoader because it implement inline functions
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
private val blockchainProvidersStorage = mockk<BlockchainProvidersStorage>()
private val loader = BlockchainProvidersResponseLoader(
tangemTechServiceApi = tangemTechServiceApi,
assetLoader = assetLoader,
blockchainProvidersStorage = blockchainProvidersStorage,
dispatchers = TestingCoroutineDispatcherProvider(),
)
@ -48,17 +36,11 @@ internal class BlockchainProvidersResponseLoaderTest {
@Test
fun test_load_if_local_config_is_empty() = runTest {
val emptyJson = ""
everyGettingLocalConfig(json = emptyJson) returns null
coEvery { blockchainProvidersStorage.getConfigSync() } returns emptyMap()
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(emptyJson)
}
coVerifyOrder { blockchainProvidersStorage.getConfigSync() }
coVerify(inverse = true) { tangemTechServiceApi.getBlockchainProviders() }
Truth.assertThat(actual).isEqualTo(null)
@ -66,15 +48,13 @@ internal class BlockchainProvidersResponseLoaderTest {
@Test
fun test_load_if_remote_config_loading_is_loaded_failure() = runTest {
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } throws IllegalStateException("Test exception")
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
@ -83,15 +63,13 @@ internal class BlockchainProvidersResponseLoaderTest {
@Test
fun test_load_if_remote_config_loading_is_loaded_successful() = runTest {
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
@ -102,15 +80,13 @@ internal class BlockchainProvidersResponseLoaderTest {
fun test_load_if_remote_config_is_the_same_as_local() = runTest {
val remoteProviders = localProviders
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
@ -122,16 +98,14 @@ internal class BlockchainProvidersResponseLoaderTest {
val ethProvider = "ethereum" to emptyList<ProviderModel>()
val remoteProvidersWithEth = remoteProviders + ethProvider
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
@ -143,15 +117,13 @@ internal class BlockchainProvidersResponseLoaderTest {
fun test_load_if_local_config_has_empty_providers() = runTest {
val localProvidersWithEmptyApt = localProviders.mapValues { if (it.key == "aptos") emptyList() else it.value }
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProvidersWithEmptyApt
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEmptyApt
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProviders
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
}
@ -164,16 +136,14 @@ internal class BlockchainProvidersResponseLoaderTest {
fun test_load_if_remote_config_doesnt_contain_local_providers() = runTest {
val remoteProvidersWithoutLocal: BlockchainProvidersResponse = remoteProviders - localProviders.keys
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithoutLocal
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
@ -186,16 +156,14 @@ internal class BlockchainProvidersResponseLoaderTest {
val ethProvider = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Private(name = "nownodes"))
val remoteProvidersWithEth = remoteProviders + ethProvider
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProviders
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProviders
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
@ -213,16 +181,14 @@ internal class BlockchainProvidersResponseLoaderTest {
val remoteEthProvider = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Public("adbw2138"))
val remoteProvidersWithEth = remoteProviders + remoteEthProvider
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProvidersWithEth
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEth
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithEth
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
@ -242,16 +208,14 @@ internal class BlockchainProvidersResponseLoaderTest {
val remoteDogeProvider = "dogecoin" to listOf(ProviderModel.Public(url = remotePublicProviderUrl))
val remoteProvidersWithDoge = remoteProviders + remoteDogeProvider
everyGettingLocalConfig(json = LOCAL_PROVIDERS_JSON) returns localProvidersWithEth
coEvery { blockchainProvidersStorage.getConfigSync() } returns localProvidersWithEth
coEvery { tangemTechServiceApi.getBlockchainProviders() } returns remoteProvidersWithDoge
everyCrashlyticsRecording() just Runs
val actual = loader.load()
coVerifyOrder {
assetReader.read(LOCAL_CONFIG_FILE_NAME)
moshi.adapter<BlockchainProvidersResponse>()
jsonAdapter.fromJson(LOCAL_PROVIDERS_JSON)
blockchainProvidersStorage.getConfigSync()
tangemTechServiceApi.getBlockchainProviders()
firebaseCrashlytics.recordException(any())
}
@ -263,21 +227,9 @@ internal class BlockchainProvidersResponseLoaderTest {
Truth.assertThat(actual).isEqualTo(expected)
}
private fun everyGettingLocalConfig(
json: String,
): MockKStubScope<BlockchainProvidersResponse?, BlockchainProvidersResponse?> {
coEvery { assetReader.read(LOCAL_CONFIG_FILE_NAME) } returns json
every { moshi.adapter<BlockchainProvidersResponse>() } returns jsonAdapter
return coEvery { jsonAdapter.fromJson(json) }
}
private fun everyCrashlyticsRecording() = every { firebaseCrashlytics.recordException(any()) }
private companion object {
const val LOCAL_CONFIG_FILE_NAME = "tangem-app-config/providers_order.json"
const val LOCAL_PROVIDERS_JSON = "doesn't matter"
val localProviders: BlockchainProvidersResponse = mapOf(
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
"algorand" to listOf(