Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-21 11:08:25 +03:00
commit f0a85e8b5b
508 changed files with 12810 additions and 8013 deletions

View file

@ -88,6 +88,8 @@ internal class DefaultAppCurrencyRepository(
Timber.e(e, "Unable to fetch available currencies")
availableAppCurrenciesStore.store(getDefaultCurrenciesResponse())
throw e
}
}

View file

@ -12,13 +12,11 @@ android {
dependencies {
/** Project - Domain */
implementation(projects.domain.core)
implementation(projects.domain.appTheme)
implementation(projects.domain.appTheme.models)
/** Project - Data */
implementation(projects.core.datasource)
implementation(projects.data.common)
/** Project - Utils */
implementation(projects.core.utils)
@ -29,6 +27,4 @@ dependencies {
/** Other */
implementation(deps.kotlin.coroutines)
implementation(deps.timber)
implementation(deps.jodatime)
}

View file

@ -0,0 +1,35 @@
package com.tangem.data.apptheme
import com.tangem.datasource.local.apptheme.AppThemeModeStore
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal class DefaultAppThemeModeRepository(
private val appThemeModeStore: AppThemeModeStore,
private val dispatchers: CoroutineDispatcherProvider,
) : AppThemeModeRepository {
override fun getAppThemeMode(): Flow<AppThemeMode> {
return channelFlow {
launch(dispatchers.io) {
if (appThemeModeStore.isEmpty()) {
appThemeModeStore.store(AppThemeMode.DEFAULT)
}
}
launch(dispatchers.io) {
appThemeModeStore.get().collect(::send)
}
}
}
override suspend fun changeAppThemeMode(mode: AppThemeMode) {
withContext(dispatchers.io) {
appThemeModeStore.store(mode)
}
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.data.apptheme
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
internal class MockAppThemeModeRepository : AppThemeModeRepository {
private val appThemeModeFlow = MutableStateFlow(AppThemeMode.DEFAULT)
override fun getAppThemeMode(): Flow<AppThemeMode> {
return appThemeModeFlow
}
override suspend fun changeAppThemeMode(mode: AppThemeMode) {
appThemeModeFlow.value = mode
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.data.apptheme.di
import com.tangem.data.apptheme.MockAppThemeModeRepository
import com.tangem.data.apptheme.DefaultAppThemeModeRepository
import com.tangem.datasource.local.apptheme.AppThemeModeStore
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -14,7 +16,10 @@ internal object AppThemeModeDataModule {
@Provides
@Singleton
fun provideAppThemeModeRepository(): AppThemeModeRepository {
return MockAppThemeModeRepository()
fun provideAppThemeModeRepository(
appThemeModeStore: AppThemeModeStore,
dispatchers: CoroutineDispatcherProvider,
): AppThemeModeRepository {
return DefaultAppThemeModeRepository(appThemeModeStore, dispatchers)
}
}

1
data/balance-hiding/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,27 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.data.balancehiding"
}
dependencies {
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.kotlin.coroutines)
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
}

View file

@ -0,0 +1,36 @@
package com.tangem.data.balancehiding
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
internal class DefaultBalanceHidingRepository(
private val balanceHidingSettingsStore: BalanceHidingSettingsStore,
private val dispatchers: CoroutineDispatcherProvider,
) : BalanceHidingRepository {
override fun getBalanceHidingSettingsFlow(): Flow<BalanceHidingSettings> {
return balanceHidingSettingsStore.get()
.onStart { emit(getBalanceHidingSettings()) }
.flowOn(dispatchers.io)
.distinctUntilChanged()
}
override suspend fun storeBalanceHidingSettings(balanceHidingSettings: BalanceHidingSettings) {
withContext(dispatchers.io) {
balanceHidingSettingsStore.store(balanceHidingSettings)
}
}
override suspend fun getBalanceHidingSettings(): BalanceHidingSettings {
return withContext(dispatchers.io) {
balanceHidingSettingsStore.getSyncOrDefault()
}
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.data.balancehiding
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorManager
import com.tangem.domain.balancehiding.DeviceFlipDetector
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
internal class DefaultDeviceFlipDetector(context: Context) : DeviceFlipDetector {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private var gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
override fun getDeviceFlipFlow(): Flow<Unit> = callbackFlow {
val listener = FlipListener { trySend(Unit) }
gravitySensor?.let {
sensorManager.registerListener(listener, it, SensorManager.SENSOR_DELAY_NORMAL)
}
awaitClose { sensorManager.unregisterListener(listener) }
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.data.balancehiding
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.os.SystemClock
internal class FlipListener(private val action: () -> Unit) : SensorEventListener {
private val zAxisThreshold = -6
private val throttleTimeMs = 3000
private var lastTriggerTime = 0L
private var isScreenDown = false
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
/* no-op */
}
override fun onSensorChanged(event: SensorEvent?) {
event?.let {
val currentTime = SystemClock.elapsedRealtime()
val zAxisValue = it.values[2]
if (zAxisValue < zAxisThreshold && !isScreenDown) {
isScreenDown = true
lastTriggerTime = currentTime
// TODO add module logging
// Timber.tag("onSensorChanged").d("screen down")
} else if (zAxisValue >= zAxisThreshold) {
if (isScreenDown && currentTime - lastTriggerTime <= throttleTimeMs) {
// Timber.tag("onSensorChanged").d("screen up!")
lastTriggerTime = currentTime
action.invoke()
}
isScreenDown = false
}
}
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.data.balancehiding.di
import android.content.Context
import com.tangem.data.balancehiding.DefaultBalanceHidingRepository
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.datasource.local.appcurrency.BalanceHidingSettingsStore
import com.tangem.domain.balancehiding.DeviceFlipDetector
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import dagger.hilt.android.qualifiers.ApplicationContext
@Module
@InstallIn(SingletonComponent::class)
internal object BalanceHidingModule {
@Provides
@Singleton
fun provideBalanceHidingRepository(
balanceHidingSettingsStore: BalanceHidingSettingsStore,
dispatchers: CoroutineDispatcherProvider,
): BalanceHidingRepository {
return DefaultBalanceHidingRepository(
balanceHidingSettingsStore = balanceHidingSettingsStore,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideFlipDetector(@ApplicationContext context: Context): DeviceFlipDetector {
return DefaultDeviceFlipDetector(context = context)
}
}

View file

@ -19,7 +19,7 @@ import javax.inject.Singleton
internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, CardSdkLifecycleObserver {
override val sdk: TangemSdk
get() = requireNotNull(value = _sdk) { "Impossible to get the TangemSdk when activity is destroyed" }
get() = requireNotNull(value = _sdk?.get()) { "Impossible to get the TangemSdk when activity is destroyed" }
private var _sdk: TangemSdk? = null

View file

@ -14,13 +14,16 @@ dependencies {
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
implementation(deps.kotlin.coroutines)
implementation(projects.core.utils)
implementation(projects.core.datasource)
implementation(projects.domain.settings)
implementation(projects.domain.balanceHiding.models)
implementation(projects.data.source.preferences)
}

View file

@ -20,6 +20,9 @@ internal object SettingsDataModule {
preferencesDataSource: PreferencesDataSource,
dispatchers: CoroutineDispatcherProvider,
): SettingsRepository {
return DefaultSettingsRepository(preferencesDataSource = preferencesDataSource, dispatchers = dispatchers)
return DefaultSettingsRepository(
preferencesDataSource = preferencesDataSource,
dispatchers = dispatchers,
)
}
}

View file

@ -17,6 +17,7 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.txhistory.models)
implementation(projects.domain.wallets.models)
/** Project - Data */
@ -40,4 +41,5 @@ dependencies {
implementation(deps.moshi.kotlin)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.retrofit) // For HttpException
}

View file

@ -2,14 +2,17 @@ package com.tangem.data.tokens.di
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
import com.tangem.data.tokens.repository.DefaultMarketCryptoCurrencyRepository
import com.tangem.data.tokens.repository.DefaultNetworksRepository
import com.tangem.data.tokens.repository.DefaultQuotesRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.appcurrency.SelectedAppCurrencyStore
import com.tangem.datasource.local.quote.QuotesStore
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -30,10 +33,18 @@ internal object TokensDataModule {
tangemTechApi: TangemTechApi,
userTokensStore: UserTokensStore,
userWalletsStore: UserWalletsStore,
userMarketCoinsStore: UserMarketCoinsStore,
cacheRegistry: CacheRegistry,
dispatchers: CoroutineDispatcherProvider,
): CurrenciesRepository {
return DefaultCurrenciesRepository(tangemTechApi, userTokensStore, userWalletsStore, cacheRegistry, dispatchers)
return DefaultCurrenciesRepository(
tangemTechApi = tangemTechApi,
userTokensStore = userTokensStore,
userWalletsStore = userWalletsStore,
userMarketCoinsStore = userMarketCoinsStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
}
@Provides
@ -46,11 +57,11 @@ internal object TokensDataModule {
dispatchers: CoroutineDispatcherProvider,
): QuotesRepository {
return DefaultQuotesRepository(
tangemTechApi,
quotesStore,
selectedAppCurrencyStore,
cacheRegistry,
dispatchers,
tangemTechApi = tangemTechApi,
quotesStore = quotesStore,
selectedAppCurrencyStore = selectedAppCurrencyStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
}
@ -64,11 +75,19 @@ internal object TokensDataModule {
dispatchers: CoroutineDispatcherProvider,
): NetworksRepository {
return DefaultNetworksRepository(
walletManagersFacade,
userWalletsStore,
userTokensStore,
cacheRegistry,
dispatchers,
walletManagersFacade = walletManagersFacade,
userWalletsStore = userWalletsStore,
userTokensStore = userTokensStore,
cacheRegistry = cacheRegistry,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideDefaultMarketCoinsRepository(
userMarketCoinsStore: UserMarketCoinsStore,
): MarketCryptoCurrencyRepository {
return DefaultMarketCryptoCurrencyRepository(userMarketCoinsStore)
}
}

View file

@ -1,17 +1,20 @@
package com.tangem.data.tokens.repository
import com.tangem.blockchain.common.Blockchain
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.CardCurrenciesFactory
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
import com.tangem.data.tokens.utils.UserTokensResponseFactory
import com.tangem.data.tokens.utils.*
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.core.error.DataError
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
@ -21,19 +24,21 @@ import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import timber.log.Timber
internal class DefaultCurrenciesRepository(
private val tangemTechApi: TangemTechApi,
private val userTokensStore: UserTokensStore,
private val userWalletsStore: UserWalletsStore,
private val userMarketCoinsStore: UserMarketCoinsStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : CurrenciesRepository {
private val demoConfig = DemoConfig()
private val responseCurrenciesFactory = ResponseCurrenciesFactory(demoConfig)
private val cardCurrenciesFactory = CardCurrenciesFactory(demoConfig)
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(demoConfig)
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig)
private val userTokensResponseFactory = UserTokensResponseFactory()
override suspend fun saveTokens(
@ -53,6 +58,54 @@ internal class DefaultCurrenciesRepository(
storeAndPushTokens(userWalletId, response)
}
override suspend fun addCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
return withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
value = userTokensStore.getSyncOrNull(userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
)
val newCoins = createCoinsForNewTokens(
userWalletId = userWalletId,
newTokens = currencies.filterIsInstance<CryptoCurrency.Token>(),
savedCurrencies = savedCurrencies.tokens,
)
val newCurrencies = newCoins + currencies
storeAndPushTokens(
userWalletId = userWalletId,
response = savedCurrencies.copy(
tokens = savedCurrencies.tokens + newCurrencies.map(userTokensResponseFactory::createResponseToken),
),
)
}
}
private suspend fun createCoinsForNewTokens(
userWalletId: UserWalletId,
newTokens: List<CryptoCurrency.Token>,
savedCurrencies: List<UserTokensResponse.Token>,
): List<CryptoCurrency.Coin> {
return newTokens
.filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins
.mapNotNull {
CryptoCurrencyFactory().createCoin(
blockchain = getBlockchain(networkId = it.network.id),
extraDerivationPath = it.network.derivationPath.value,
derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider,
)
}
}
private fun List<UserTokensResponse.Token>.hasCoinForToken(token: CryptoCurrency.Token): Boolean {
return any {
val blockchain = getBlockchain(networkId = token.network.id)
it.id == blockchain.toCoinId()
}
}
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: CryptoCurrency) =
withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
@ -69,6 +122,23 @@ internal class DefaultCurrenciesRepository(
)
}
override suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
return withContext(dispatchers.io) {
val savedCurrencies = requireNotNull(
value = userTokensStore.getSyncOrNull(userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform remove currencies action" },
)
val tokens = currencies.map(userTokensResponseFactory::createResponseToken)
storeAndPushTokens(
userWalletId = userWalletId,
response = savedCurrencies.copy(
tokens = savedCurrencies.tokens.filterNot(tokens::contains),
),
)
}
}
override suspend fun getSingleCurrencyWalletPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency {
return withContext(dispatchers.io) {
val userWallet = getUserWallet(userWalletId)
@ -106,10 +176,7 @@ internal class DefaultCurrenciesRepository(
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
return responseCurrenciesFactory.createCurrencies(
response = storedTokens,
card = userWallet.scanResponse.card,
)
return responseCurrenciesFactory.createCurrencies(storedTokens, userWallet.scanResponse)
}
override suspend fun getMultiCurrencyWalletCurrency(
@ -123,7 +190,25 @@ internal class DefaultCurrenciesRepository(
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
responseCurrenciesFactory.createCurrency(id, response, userWallet.scanResponse.card)
responseCurrenciesFactory.createCurrency(id, response, userWallet.scanResponse)
}
override suspend fun getNetworkCoin(userWalletId: UserWalletId, networkId: Network.ID): CryptoCurrency.Coin {
val userWallet = getUserWallet(userWalletId)
ensureIsCorrectUserWallet(userWallet = userWallet, isMultiCurrencyWalletExpected = true)
fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false)
val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
val storedCoin = storedTokens.tokens.find { it.networkId == Blockchain.fromId(networkId.value).toNetworkId() }
?: error("Coin in this network $networkId not found")
val coin = responseCurrenciesFactory.createCurrency(storedCoin, userWallet.scanResponse)
return coin as? CryptoCurrency.Coin ?: error("Unable to create currency")
}
override fun isTokensGrouped(userWalletId: UserWalletId): Flow<Boolean> {
@ -154,7 +239,7 @@ internal class DefaultCurrenciesRepository(
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies(
response = storedTokens,
card = userWallet.scanResponse.card,
scanResponse = userWallet.scanResponse,
)
}
}
@ -168,13 +253,19 @@ internal class DefaultCurrenciesRepository(
}
private suspend fun fetchTokens(userWallet: UserWallet) {
try {
val response = tangemTechApi.getUserTokens(userWallet.walletId.stringValue)
val userWalletId = userWallet.walletId
userTokensStore.store(userWallet.walletId, response)
} catch (e: Throwable) {
handleFetchTokensErrorOrThrow(userWallet, e)
val response = try {
with(tangemTechApi.getUserTokens(userWalletId.stringValue)) {
// The response may contain repeated tokens
copy(tokens = tokens.distinct())
}
} catch (e: HttpException) {
handleCurrenciesNotFoundOrThrow(userWallet, e)
}
userTokensStore.store(userWallet.walletId, response)
fetchUserMarketCoinsByIds(userWalletId, response)
}
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
@ -182,26 +273,42 @@ internal class DefaultCurrenciesRepository(
tangemTechApi.saveUserTokens(userWalletId.stringValue, response)
}
private suspend fun handleFetchTokensErrorOrThrow(userWallet: UserWallet, error: Throwable) {
val errorMessage = error.message ?: throw error
private suspend fun fetchUserMarketCoinsByIds(userWalletId: UserWalletId, userTokens: UserTokensResponse) {
try {
val networkIds = userTokens.tokens.joinToString(separator = ",") { it.networkId }
val response = tangemTechApi.getCoins(networkIds)
if (NOT_FOUND_HTTP_CODE in errorMessage) {
val response = userTokensStore.getSyncOrNull(userWallet.walletId)
?: userTokensResponseFactory.createUserTokensResponse(
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(
card = userWallet.scanResponse.card,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
tangemTechApi.saveUserTokens(userWallet.walletId.stringValue, response)
} else {
Timber.e(error, "Unable to fetch currencies for: ${userWallet.walletId}")
userMarketCoinsStore.store(userWalletId, response)
} catch (e: Throwable) {
Timber.e(e, "Unable to fetch user market coins for: ${userWalletId.stringValue}")
}
}
private suspend fun handleCurrenciesNotFoundOrThrow(
userWallet: UserWallet,
httpException: HttpException,
): UserTokensResponse {
val userWalletId = userWallet.walletId
if (httpException.code() != NOT_FOUND_HTTP_CODE) {
Timber.e(httpException, "Unable to fetch currencies for: $userWalletId")
throw httpException
}
Timber.d("Requested currencies could not be found in the remote store for: $userWalletId")
val response = userTokensStore.getSyncOrNull(userWalletId)
?: userTokensResponseFactory.createUserTokensResponse(
currencies = cardCurrenciesFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
isGroupedByNetwork = false,
isSortedByBalance = false,
)
tangemTechApi.saveUserTokens(userWalletId.stringValue, response)
return response
}
private suspend fun getUserWallet(userWalletId: UserWalletId): UserWallet {
return requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find a user wallet with provided ID: $userWalletId"
@ -238,6 +345,6 @@ internal class DefaultCurrenciesRepository(
private fun getTokensCacheKey(userWalletId: UserWalletId): String = "tokens_cache_key_${userWalletId.stringValue}"
private companion object {
const val NOT_FOUND_HTTP_CODE = "404"
const val NOT_FOUND_HTTP_CODE = 404
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.data.tokens.repository
import com.tangem.datasource.local.token.UserMarketCoinsStore
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
import com.tangem.domain.wallets.models.UserWalletId
class DefaultMarketCryptoCurrencyRepository(
private val userMarketCoinsStore: UserMarketCoinsStore,
) : MarketCryptoCurrencyRepository {
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Boolean {
return userMarketCoinsStore.getSyncOrNull(userWalletId)?.coins
?.firstOrNull { it.id == cryptoCurrencyId.rawCurrencyId }
?.networks
?.firstOrNull { it.networkId == cryptoCurrencyId.rawNetworkId }?.exchangeable ?: false
}
}

View file

@ -1,10 +1,9 @@
package com.tangem.data.tokens.repository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.CardCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkConverter
import com.tangem.data.tokens.utils.CardCryptoCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkStatusFactory
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
import com.tangem.data.tokens.utils.ResponseCryptoCurrenciesFactory
import com.tangem.datasource.local.token.UserTokensStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.demo.DemoConfig
@ -28,25 +27,18 @@ internal class DefaultNetworksRepository(
) : NetworksRepository {
private val demoConfig by lazy { DemoConfig() }
private val networkConverter by lazy { NetworkConverter() }
private val cardCurrenciesFactory by lazy { CardCurrenciesFactory(demoConfig) }
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(demoConfig) }
private val cardCurrenciesFactory by lazy { CardCryptoCurrenciesFactory(demoConfig) }
private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory(demoConfig) }
private val networkStatusFactory by lazy { NetworkStatusFactory() }
private val networksStatuses: MutableStateFlow<List<NetworkStatus>> = MutableStateFlow(emptyList())
override fun getNetworks(networksIds: Set<Network.ID>): Set<Network> {
return networkConverter.convertSet(networksIds)
}
private val networksStatuses: MutableStateFlow<Set<NetworkStatus>> = MutableStateFlow(hashSetOf())
override fun getNetworkStatusesUpdates(
userWalletId: UserWalletId,
networks: Set<Network.ID>,
networks: Set<Network>,
): Flow<Set<NetworkStatus>> = channelFlow {
launch(dispatchers.io) {
networksStatuses.collect {
send(it.toSet())
}
networksStatuses.collect(::send)
}
launch(dispatchers.io) {
@ -56,7 +48,7 @@ internal class DefaultNetworksRepository(
override suspend fun getNetworkStatusesSync(
userWalletId: UserWalletId,
networks: Set<Network.ID>,
networks: Set<Network>,
refresh: Boolean,
): Set<NetworkStatus> = withContext(dispatchers.io) {
fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh)
@ -65,66 +57,86 @@ internal class DefaultNetworksRepository(
private suspend fun fetchNetworksStatusesIfCacheExpired(
userWalletId: UserWalletId,
networks: Set<Network.ID>,
networks: Set<Network>,
refresh: Boolean,
) {
cacheRegistry.invokeOnExpire(
key = getNetworksStatusesCacheKey(userWalletId),
skipCache = refresh,
block = { fetchNetworksStatuses(userWalletId, networks) },
)
}
private suspend fun fetchNetworksStatuses(userWalletId: UserWalletId, networks: Set<Network.ID>) {
coroutineScope {
networks
.map { networkId ->
.map { network ->
async {
fetchNetworkStatus(userWalletId, networkId)
fetchNetworkStatusIfCacheExpired(userWalletId, network, refresh)
}
}
.awaitAll()
}
}
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, networkId: Network.ID) {
val currencies = getCurrencies(userWalletId)
.asSequence()
.filter { it.network.id == networkId }
private suspend fun fetchNetworkStatusIfCacheExpired(
userWalletId: UserWalletId,
network: Network,
refresh: Boolean,
) {
cacheRegistry.invokeOnExpire(
key = getNetworksStatusesCacheKey(userWalletId, network),
skipCache = refresh,
block = { fetchNetworkStatus(userWalletId, network) },
)
}
private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network) {
val currencies = getCurrencies(userWalletId, network)
val result = walletManagersFacade.update(
userWalletId = userWalletId,
networkId = networkId,
network = network,
extraTokens = currencies.filterIsInstance<CryptoCurrency.Token>().toSet(),
)
val networkStatus = networkStatusFactory.createNetworkStatus(
networkId = networkId,
network = network,
result = result,
currencies = currencies.toSet(),
)
networksStatuses.update { statuses ->
statuses.addOrReplace(networkStatus) { it.networkId == networkStatus.networkId }
statuses.addOrReplace(networkStatus) { it.network == networkStatus.network }
}
invalidateCacheKeyIfNeeded(userWalletId, networkStatus)
}
private suspend fun getCurrencies(userWalletId: UserWalletId): List<CryptoCurrency> {
private suspend fun getCurrencies(userWalletId: UserWalletId, network: Network): Sequence<CryptoCurrency> {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find user wallet with provided ID: $userWalletId"
}
return if (userWallet.isMultiCurrency) {
val currencies = if (userWallet.isMultiCurrency) {
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse.card)
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse).asSequence()
} else {
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
listOf(currency)
sequenceOf(currency)
}
return currencies.filter { it.network == network }
}
private suspend fun invalidateCacheKeyIfNeeded(userWalletId: UserWalletId, networkStatus: NetworkStatus) {
when (networkStatus.value) {
is NetworkStatus.Verified,
is NetworkStatus.NoAccount,
-> Unit
is NetworkStatus.Unreachable,
is NetworkStatus.MissedDerivation,
-> cacheRegistry.invalidate(getNetworksStatusesCacheKey(userWalletId, networkStatus.network))
}
}
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId"
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId, network: Network): String {
return "network_status_${userWalletId}_${network.id}_${network.derivationPath.value}"
}
}

View file

@ -0,0 +1,61 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.models.CryptoCurrency
internal class CardCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
val card = scanResponse.card
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
}
return blockchains.mapNotNull {
cryptoCurrencyFactory.createCoin(
blockchain = it,
extraDerivationPath = null,
derivationStyleProvider = cardDerivationStyleProvider,
)
}
}
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = cryptoCurrencyFactory.createCoin(
blockchain = blockchain,
extraDerivationPath = null,
derivationStyleProvider = cardDerivationStyleProvider,
)
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
val primaryToken = resolver.getPrimaryToken()?.let { token ->
cryptoCurrencyFactory.createToken(
sdkToken = token,
blockchain = blockchain,
extraDerivationPath = null,
derivationStyleProvider = cardDerivationStyleProvider,
)
}
return primaryToken ?: coin
}
}

View file

@ -1,48 +0,0 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.models.CryptoCurrency
internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
fun createDefaultCoinsForMultiCurrencyCard(
card: CardDTO,
derivationStyleProvider: DerivationStyleProvider,
): List<CryptoCurrency.Coin> {
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
demoConfig.demoBlockchains
} else {
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
}
if (card.isTestCard) {
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
}
return blockchains.mapNotNull { cryptoCurrencyFactory.createCoin(it, derivationStyleProvider) }
}
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
val derivationStyleProvider = scanResponse.derivationStyleProvider
val resolver = scanResponse.cardTypesResolver
val blockchain = resolver.getBlockchain()
val coin = requireNotNull(cryptoCurrencyFactory.createCoin(blockchain, derivationStyleProvider)) {
"Coin for the single currency card cannot be null"
}
val primaryToken = resolver.getPrimaryToken()?.let { token ->
cryptoCurrencyFactory.createToken(token, blockchain, derivationStyleProvider)
}
return primaryToken ?: coin
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.tokens.models.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
@ -12,6 +13,7 @@ class CryptoCurrencyFactory {
fun createToken(
sdkToken: SdkToken,
blockchain: Blockchain,
extraDerivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Token? {
if (blockchain == Blockchain.Unknown) {
@ -19,35 +21,40 @@ class CryptoCurrencyFactory {
return null
}
val id = getTokenId(blockchain, sdkToken)
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
val id = getTokenId(network, sdkToken)
return CryptoCurrency.Token(
id = id,
network = getNetwork(blockchain) ?: return null,
network = network,
name = sdkToken.name,
symbol = sdkToken.symbol,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
decimals = sdkToken.decimals,
isCustom = isCustomToken(id),
isCustom = isCustomToken(id, network),
contractAddress = sdkToken.contractAddress,
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
)
}
fun createCoin(blockchain: Blockchain, derivationStyleProvider: DerivationStyleProvider): CryptoCurrency.Coin? {
fun createCoin(
blockchain: Blockchain,
extraDerivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Coin? {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}
val network = getNetwork(blockchain, extraDerivationPath, derivationStyleProvider) ?: return null
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
network = getNetwork(blockchain) ?: return null,
id = getCoinId(network, blockchain.toCoinId()),
network = network,
name = blockchain.fullName,
symbol = blockchain.currency,
iconUrl = getCoinIconUrl(blockchain),
decimals = blockchain.decimals(),
derivationPath = getDerivationPath(blockchain, derivationStyleProvider),
isCustom = isCustomCoin(network),
)
}
}

View file

@ -1,22 +0,0 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.tokens.models.Network
import com.tangem.utils.converter.Converter
internal class NetworkConverter : Converter<Network.ID, Network?> {
override fun convert(value: Network.ID): Network? {
val blockchain = Blockchain.fromId(value.value)
return getNetwork(blockchain)
}
override fun convertList(input: Collection<Network.ID>): List<Network> {
return input.mapNotNull(::convert)
}
override fun convertSet(input: Collection<Network.ID>): Set<Network> {
return input.mapNotNullTo(hashSetOf(), ::convert)
}
}

View file

@ -1,10 +1,19 @@
package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.tokens.models.Network
import timber.log.Timber
internal fun getNetwork(blockchain: Blockchain): Network? {
internal fun getBlockchain(networkId: Network.ID): Blockchain {
return Blockchain.fromId(networkId.value)
}
internal fun getNetwork(
blockchain: Blockchain,
extraDerivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): Network? {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to convert Unknown blockchain to the domain network model")
return null
@ -14,10 +23,33 @@ internal fun getNetwork(blockchain: Blockchain): Network? {
id = Network.ID(blockchain.id),
name = blockchain.fullName,
isTestnet = blockchain.isTestnet(),
derivationPath = getNetworkDerivationPath(blockchain, extraDerivationPath, derivationStyleProvider),
standardType = getNetworkStandardType(blockchain),
)
}
private fun getNetworkDerivationPath(
blockchain: Blockchain,
extraDerivationPath: String?,
cardDerivationStyleProvider: DerivationStyleProvider,
): Network.DerivationPath {
val defaultDerivationPath = getDefaultDerivationPath(blockchain, cardDerivationStyleProvider)
return if (extraDerivationPath.isNullOrBlank()) {
if (defaultDerivationPath.isNullOrBlank()) {
Network.DerivationPath.None
} else {
Network.DerivationPath.Card(defaultDerivationPath)
}
} else {
if (extraDerivationPath == defaultDerivationPath) {
Network.DerivationPath.Card(defaultDerivationPath)
} else {
Network.DerivationPath.Custom(extraDerivationPath)
}
}
}
private fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType {
return when (blockchain) {
Blockchain.Ethereum, Blockchain.EthereumTestnet -> Network.StandardType.ERC20
@ -26,4 +58,11 @@ private fun getNetworkStandardType(blockchain: Blockchain): Network.StandardType
Blockchain.Tron, Blockchain.TronTestnet -> Network.StandardType.TRC20
else -> Network.StandardType.Unspecified(blockchain.name)
}
}
private fun getDefaultDerivationPath(
blockchain: Blockchain,
derivationStyleProvider: DerivationStyleProvider,
): String? {
return blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath
}

View file

@ -2,9 +2,9 @@ package com.tangem.data.tokens.utils
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.PendingTransaction
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
@ -14,12 +14,12 @@ import java.math.BigDecimal
internal class NetworkStatusFactory {
fun createNetworkStatus(
networkId: Network.ID,
network: Network,
result: UpdateWalletManagerResult,
currencies: Set<CryptoCurrency>,
): NetworkStatus {
return NetworkStatus(
networkId = networkId,
network = network,
value = when (result) {
is UpdateWalletManagerResult.MissedDerivation -> NetworkStatus.MissedDerivation
is UpdateWalletManagerResult.Unreachable -> NetworkStatus.Unreachable
@ -31,7 +31,6 @@ internal class NetworkStatusFactory {
address = getNetworkAddress(result.defaultAddress, result.addresses),
amounts = formatAmounts(result.currenciesAmounts, currencies),
pendingTransactions = formatTransactions(
networksAddresses = result.addresses,
transactions = result.currentTransactions,
currencies = currencies,
),
@ -67,10 +66,9 @@ internal class NetworkStatusFactory {
}
private fun formatTransactions(
networksAddresses: Set<String>,
transactions: Set<CryptoCurrencyTransaction>,
currencies: Set<CryptoCurrency>,
): Map<CryptoCurrency.ID, Set<PendingTransaction>> {
): Map<CryptoCurrency.ID, Set<TxHistoryItem>> {
if (transactions.isEmpty()) return emptyMap()
return currencies
@ -87,48 +85,13 @@ internal class NetworkStatusFactory {
}
}
currency.id to createCurrentTransactions(networksAddresses, currencyTransactions)
currency.id to createCurrentTransactions(currencyTransactions)
}
.toMap()
}
private fun createCurrentTransactions(
networksAddresses: Set<String>,
transactions: Set<CryptoCurrencyTransaction>,
): Set<PendingTransaction> {
return transactions.mapNotNullTo(hashSetOf()) { createCurrentTransaction(networksAddresses, it) }
}
private fun createCurrentTransaction(
networksAddresses: Set<String>,
transaction: CryptoCurrencyTransaction,
): PendingTransaction? {
val direction = when {
transaction.toAddress in networksAddresses -> PendingTransaction.Direction.Incoming(
fromAddress = transaction.fromAddress,
)
transaction.fromAddress in networksAddresses -> PendingTransaction.Direction.Outgoing(
toAddress = transaction.toAddress,
)
else -> {
Timber.e(
"""
Unable to find transaction direction
|- To address: ${transaction.toAddress}
|- From address: ${transaction.fromAddress}
|- Network addresses: $networksAddresses
""".trimIndent(),
)
return null
}
}
return PendingTransaction(
amount = transaction.amount,
direction = direction,
sentAt = transaction.sentAt,
)
private fun createCurrentTransactions(transactions: Set<CryptoCurrencyTransaction>): Set<TxHistoryItem> {
return transactions.mapTo(hashSetOf()) { it.txHistoryItem }
}
private fun getNetworkAddress(defaultAddress: String, availableAddresses: Set<String>): NetworkAddress {

View file

@ -3,47 +3,61 @@ package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.models.CryptoCurrency
import timber.log.Timber
import com.tangem.blockchain.common.Token as SdkToken
internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
internal class ResponseCryptoCurrenciesFactory(private val demoConfig: DemoConfig) {
fun createCurrency(currencyId: CryptoCurrency.ID, response: UserTokensResponse, card: CardDTO): CryptoCurrency {
fun createCurrency(
currencyId: CryptoCurrency.ID,
response: UserTokensResponse,
scanResponse: ScanResponse,
): CryptoCurrency {
val responseTokenId = currencyId.rawCurrencyId
val token = requireNotNull(response.tokens.firstOrNull { it.id == responseTokenId }) {
"Unable find a token with provided ID: $responseTokenId"
}
return requireNotNull(createCurrency(token, card)) {
return requireNotNull(createCurrency(token, scanResponse)) {
"Unable to create a currency with provided ID: $currencyId"
}
}
fun createCurrencies(response: UserTokensResponse, card: CardDTO): List<CryptoCurrency> {
return response.tokens.mapNotNull { createCurrency(it, card) }
fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List<CryptoCurrency> {
return response.tokens
.asSequence()
.mapNotNull { createCurrency(it, scanResponse) }
.distinctBy { it.id }
.toList()
}
private fun createCurrency(responseToken: UserTokensResponse.Token, card: CardDTO): CryptoCurrency? {
fun createCurrency(responseToken: UserTokensResponse.Token, scanResponse: ScanResponse): CryptoCurrency? {
var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")
return null
}
val cardDerivationStyleProvider = scanResponse.derivationStyleProvider
val card = scanResponse.card
if (demoConfig.isDemoCardId(card.cardId)) {
blockchain = blockchain.getTestnetVersion() ?: blockchain
}
val sdkToken = createSdkToken(responseToken)
return if (sdkToken == null) {
createCoin(blockchain, responseToken)
createCoin(blockchain, responseToken, cardDerivationStyleProvider)
} else {
createToken(blockchain, sdkToken, responseToken.derivationPath)
createToken(blockchain, sdkToken, responseToken.derivationPath, cardDerivationStyleProvider)
}
}
@ -59,31 +73,43 @@ internal class ResponseCurrenciesFactory(private val demoConfig: DemoConfig) {
}
}
private fun createCoin(blockchain: Blockchain, responseToken: UserTokensResponse.Token): CryptoCurrency.Coin? {
private fun createCoin(
blockchain: Blockchain,
responseToken: UserTokensResponse.Token,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Coin? {
val network = getNetwork(blockchain, responseToken.derivationPath, derivationStyleProvider) ?: return null
return CryptoCurrency.Coin(
id = getCoinId(blockchain),
network = getNetwork(blockchain) ?: return null,
id = getCoinId(network, blockchain.toCoinId()),
network = network,
name = responseToken.name,
symbol = responseToken.symbol,
decimals = responseToken.decimals,
derivationPath = responseToken.derivationPath,
iconUrl = getCoinIconUrl(blockchain),
isCustom = isCustomCoin(network),
)
}
private fun createToken(blockchain: Blockchain, sdkToken: Token, derivationPath: String?): CryptoCurrency.Token? {
val id = getTokenId(blockchain, sdkToken)
private fun createToken(
blockchain: Blockchain,
sdkToken: Token,
responseDerivationPath: String?,
derivationStyleProvider: DerivationStyleProvider,
): CryptoCurrency.Token? {
val network = getNetwork(blockchain, responseDerivationPath, derivationStyleProvider)
?: return null
val id = getTokenId(network, sdkToken)
return CryptoCurrency.Token(
id = id,
network = getNetwork(blockchain) ?: return null,
network = network,
name = sdkToken.name,
symbol = sdkToken.symbol,
decimals = sdkToken.decimals,
derivationPath = derivationPath,
iconUrl = getTokenIconUrl(blockchain, sdkToken),
contractAddress = sdkToken.contractAddress,
isCustom = isCustomToken(id),
isCustom = isCustomToken(id, network),
)
}
}

View file

@ -2,14 +2,13 @@ package com.tangem.data.tokens.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.IconsUtil
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.toCoinId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.tokens.models.CryptoCurrency.ID
import com.tangem.domain.tokens.models.Network
import com.tangem.blockchain.common.Token as SdkToken
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Body as CurrencyIdBody
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Prefix.COIN_PREFIX as COIN_ID_PREFIX
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Prefix.CUSTOM_TOKEN_PREFIX as CUSTOM_TOKEN_ID_PREFIX
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Prefix.TOKEN_PREFIX as TOKEN_ID_PREFIX
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Suffix.ContractAddress as CustomCurrencyIdSuffix
import com.tangem.domain.tokens.models.CryptoCurrency.ID.Suffix.RawID as CurrencyIdSuffix
@ -18,24 +17,27 @@ private const val DEFAULT_TOKENS_ICONS_HOST = "https://s3.eu-central-1.amazonaws
private const val TOKEN_ICON_SIZE = "large"
private const val TOKEN_ICON_EXT = "png"
internal fun isCustomToken(tokenId: ID): Boolean {
return tokenId.rawCurrencyId == null
internal fun isCustomToken(tokenId: ID, network: Network): Boolean {
return network.derivationPath is Network.DerivationPath.Custom || tokenId.rawCurrencyId == null
}
internal fun getDerivationPath(blockchain: Blockchain, derivationStyleProvider: DerivationStyleProvider): String? {
return blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath
internal fun isCustomCoin(network: Network): Boolean {
return network.derivationPath is Network.DerivationPath.Custom
}
internal fun getBlockchain(networkId: Network.ID): Blockchain {
return Blockchain.fromId(networkId.value)
internal fun getCoinId(network: Network, coinId: String): ID {
return ID(COIN_ID_PREFIX, getCurrencyIdBody(network), CurrencyIdSuffix(rawId = coinId))
}
internal fun getCoinId(blockchain: Blockchain): ID {
return getTokenOrCoinId(blockchain, token = null)
}
internal fun getTokenId(network: Network, sdkToken: SdkToken): ID {
val sdkTokenId = sdkToken.id
val suffix = if (sdkTokenId == null) {
CustomCurrencyIdSuffix(contractAddress = sdkToken.contractAddress)
} else {
CurrencyIdSuffix(rawId = sdkTokenId)
}
internal fun getTokenId(blockchain: Blockchain, token: SdkToken): ID {
return getTokenOrCoinId(blockchain, token)
return ID(TOKEN_ID_PREFIX, getCurrencyIdBody(network), suffix)
}
internal fun getTokenIconUrl(blockchain: Blockchain, token: SdkToken): String? {
@ -58,15 +60,16 @@ internal fun getCoinIconUrl(blockchain: Blockchain): String? {
return coinId?.let(::getTokenIconUrlFromDefaultHost)
}
private fun getTokenOrCoinId(blockchain: Blockchain, token: SdkToken?): ID {
val sdkTokenId = token?.id
val (prefix, suffix) = when {
token == null -> COIN_ID_PREFIX to CurrencyIdSuffix(rawId = blockchain.toCoinId())
sdkTokenId == null -> CUSTOM_TOKEN_ID_PREFIX to CustomCurrencyIdSuffix(contractAddress = token.contractAddress)
else -> TOKEN_ID_PREFIX to CurrencyIdSuffix(rawId = sdkTokenId)
private fun getCurrencyIdBody(network: Network): CurrencyIdBody {
return when (val path = network.derivationPath) {
is Network.DerivationPath.Custom -> CurrencyIdBody.NetworkIdWithDerivationPath(
rawId = network.id.value,
derivationPath = path.value,
)
is Network.DerivationPath.Card,
is Network.DerivationPath.None,
-> CurrencyIdBody.NetworkId(network.id.value)
}
return ID(prefix, Network.ID(blockchain.id), suffix)
}
private fun getTokenIconUrlFromDefaultHost(tokenId: String): String {

View file

@ -32,7 +32,7 @@ internal class UserTokensResponseFactory {
return UserTokensResponse.Token(
id = currency.id.rawCurrencyId,
networkId = blockchain.toNetworkId(),
derivationPath = currency.derivationPath,
derivationPath = currency.network.derivationPath.value,
name = currency.name,
symbol = currency.symbol,
decimals = currency.decimals,

View file

@ -19,12 +19,11 @@ class DefaultTxHistoryRepository(
private val userWalletsStore: UserWalletsStore,
) : TxHistoryRepository {
override suspend fun getTxHistoryItemsCount(networkId: Network.ID, derivationPath: String?): Int {
override suspend fun getTxHistoryItemsCount(network: Network): Int {
val userWallet = getUserWallet()
val state = walletManagersFacade.getTxHistoryState(
userWalletId = userWallet.walletId,
networkId = networkId,
rawDerivationPath = derivationPath,
network = network,
)
return when (state) {
is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception)
@ -34,11 +33,7 @@ class DefaultTxHistoryRepository(
}
}
override fun getTxHistoryItems(
networkId: Network.ID,
derivationPath: String?,
pageSize: Int,
): Flow<PagingData<TxHistoryItem>> {
override fun getTxHistoryItems(network: Network, pageSize: Int): Flow<PagingData<TxHistoryItem>> {
val userWallet = getUserWallet()
return Pager(
config = PagingConfig(
@ -49,8 +44,7 @@ class DefaultTxHistoryRepository(
loadPage = { page: Int, pageSize: Int ->
walletManagersFacade.getTxHistoryItems(
userWalletId = userWallet.walletId,
networkId = networkId,
rawDerivationPath = derivationPath,
network = network,
page = page,
pageSize = pageSize,
)