Updated on 2026-08-14
This commit is contained in:
parent
dc4e8c740b
commit
fefe37a890
23 changed files with 285 additions and 140 deletions
|
|
@ -190,6 +190,7 @@ class TapApplication : Application(), ImageLoaderFactory {
|
|||
scanCardProcessor = scanCardProcessor,
|
||||
appCurrencyRepository = appCurrencyRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
appStateHolder = appStateHolder,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
|
|||
sellService = makeSellExchangeService(config),
|
||||
primaryRules = CardExchangeRules(cardProvider),
|
||||
)
|
||||
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
|
||||
store.state.daggerGraphState.get(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package com.tangem.tap.di
|
|||
import android.content.Context
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -40,4 +43,10 @@ internal object ActivityModule {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDefaultRampManager(appStateHolder: AppStateHolder): RampStateManager {
|
||||
return DefaultRampManager(appStateHolder.exchangeService)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.tokens.*
|
||||
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.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -108,8 +110,10 @@ internal object TokensDomainModule {
|
|||
@Provides
|
||||
@ViewModelScoped
|
||||
fun provideGetCryptoCurrencyActionsUseCase(
|
||||
rampStateManager: RampStateManager,
|
||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(dispatchers)
|
||||
return GetCryptoCurrencyActionsUseCase(rampStateManager, marketCryptoCurrencyRepository, dispatchers)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
package com.tangem.tap.domain.tokens
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.asset.AssetReader
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class LoadAvailableCoinsService(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val assetReader: AssetReader,
|
||||
) {
|
||||
private val currenciesAdapter: JsonAdapter<CurrenciesFromJson> =
|
||||
MoshiConverter.networkMoshi.adapter(CurrenciesFromJson::class.java)
|
||||
|
||||
suspend fun getSupportedTokens(
|
||||
isTestNet: Boolean,
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
page: Int,
|
||||
searchInput: String?,
|
||||
): Result<LoadedCoins> {
|
||||
if (isTestNet) {
|
||||
return Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = getTestnetCoins().filter(searchInput),
|
||||
moreAvailable = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val offset = page * LOAD_PER_PAGE
|
||||
return when (val result = loadCoins(supportedBlockchains, offset, searchInput)) {
|
||||
is Result.Success -> {
|
||||
val data = result.data
|
||||
|
||||
Result.Success(
|
||||
LoadedCoins(
|
||||
currencies = data.coins.map {
|
||||
Currency.fromCoinResponse(currency = it, imageHost = data.imageHost)
|
||||
},
|
||||
moreAvailable = data.total > offset + LOAD_PER_PAGE,
|
||||
),
|
||||
)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Result.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadCoins(
|
||||
supportedBlockchains: List<Blockchain>,
|
||||
offset: Int,
|
||||
searchInput: String?,
|
||||
): Result<CoinsResponse> {
|
||||
return withContext(dispatchers.io) {
|
||||
runCatching {
|
||||
tangemTechApi.getCoins(
|
||||
networkIds = supportedBlockchains.joinToString(
|
||||
separator = ",",
|
||||
transform = Blockchain::toNetworkId,
|
||||
),
|
||||
active = true,
|
||||
searchText = searchInput,
|
||||
offset = offset,
|
||||
limit = LOAD_PER_PAGE,
|
||||
)
|
||||
}.fold(
|
||||
onSuccess = { Result.Success(it) },
|
||||
onFailure = { Result.Failure(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTestnetCoins(): List<Currency> {
|
||||
val json = assetReader.readJson(FILE_NAME_TESTNET_COINS)
|
||||
return currenciesAdapter.fromJson(json)!!.coins
|
||||
.map { Currency.fromJsonObject(it) }
|
||||
}
|
||||
|
||||
private fun List<Currency>.filter(searchInput: String?): List<Currency> {
|
||||
if (searchInput.isNullOrBlank()) return this
|
||||
|
||||
return filter { currency ->
|
||||
currency.symbol.contains(searchInput, ignoreCase = true) ||
|
||||
currency.name.contains(searchInput, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val LOAD_PER_PAGE = 100
|
||||
const val FILE_NAME_TESTNET_COINS = "testnet_tokens"
|
||||
}
|
||||
}
|
||||
|
||||
data class LoadedCoins(
|
||||
val currencies: List<Currency>,
|
||||
val moreAvailable: Boolean,
|
||||
)
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
package com.tangem.tap.features.wallet.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
|
||||
internal class CryptoCurrencyConverter : TwoWayConverter<Currency, CryptoCurrency> {
|
||||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory() }
|
||||
|
||||
|
|
@ -40,4 +42,26 @@ internal class CryptoCurrencyConverter : Converter<Currency, CryptoCurrency> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: CryptoCurrency): Currency {
|
||||
val blockchain = Blockchain.fromId(value.network.id.value)
|
||||
if (blockchain == Blockchain.Unknown) error("CryptoCurrencyConverter convertBack Unknown blockchain")
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> Currency.Blockchain(
|
||||
blockchain = blockchain,
|
||||
derivationPath = value.derivationPath,
|
||||
)
|
||||
is CryptoCurrency.Token -> Currency.Token(
|
||||
token = Token(
|
||||
name = value.name,
|
||||
symbol = value.symbol,
|
||||
contractAddress = value.contractAddress,
|
||||
decimals = value.decimals,
|
||||
id = value.id.value,
|
||||
),
|
||||
blockchain = blockchain,
|
||||
derivationPath = value.derivationPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.tap.network.exchangeServices
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.tap.features.wallet.converters.CryptoCurrencyConverter
|
||||
|
||||
class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager {
|
||||
|
||||
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
|
||||
override fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return exchangeService?.availableForBuy(
|
||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||
) ?: false
|
||||
}
|
||||
|
||||
override fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return exchangeService?.availableForSell(
|
||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||
) ?: false
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.tap.domain.TangemSdkManager
|
|||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.walletStores.WalletStoresManager
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -45,6 +46,7 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, ReduxNavControl
|
|||
var tangemSdkManager: TangemSdkManager? = null
|
||||
var walletStoresManager: WalletStoresManager? = null
|
||||
var appFiatCurrency: FiatCurrency = FiatCurrency.Default
|
||||
var exchangeService: ExchangeService? = null
|
||||
|
||||
fun getActualCard(): CardDTO? {
|
||||
return scanResponse?.card
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
|||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class DaggerGraphState(
|
||||
|
|
@ -35,6 +36,7 @@ data class DaggerGraphState(
|
|||
val cardSdkConfigRepository: CardSdkConfigRepository? = null,
|
||||
val appCurrencyRepository: AppCurrencyRepository? = null,
|
||||
val walletManagersFacade: WalletManagersFacade? = null,
|
||||
val appStateHolder: AppStateHolder? = null,
|
||||
) : StateType {
|
||||
|
||||
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultUserMarketCoinsStore
|
||||
import com.tangem.datasource.local.token.UserMarketCoinsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object MarketCoinsStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserMarketCoinsStore(): UserMarketCoinsStore {
|
||||
return DefaultUserMarketCoinsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal class DefaultUserMarketCoinsStore(
|
||||
private val dataStore: StringKeyDataStore<CoinsResponse>,
|
||||
) : UserMarketCoinsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun store(userWalletId: UserWalletId, item: CoinsResponse) {
|
||||
dataStore.store(userWalletId.stringValue, item)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface UserMarketCoinsStore {
|
||||
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): CoinsResponse?
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, item: CoinsResponse)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
|
|||
import com.tangem.data.tokens.utils.UserTokensResponseFactory
|
||||
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.util.derivationStyleProvider
|
||||
|
|
@ -27,6 +28,7 @@ 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 {
|
||||
|
|
@ -172,6 +174,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val response = tangemTechApi.getUserTokens(userWallet.walletId.stringValue)
|
||||
|
||||
userTokensStore.store(userWallet.walletId, response)
|
||||
fetchUserMarketCoinsByIds(userWallet.walletId, response)
|
||||
} catch (e: Throwable) {
|
||||
handleFetchTokensErrorOrThrow(userWallet, e)
|
||||
}
|
||||
|
|
@ -182,6 +185,17 @@ internal class DefaultCurrenciesRepository(
|
|||
tangemTechApi.saveUserTokens(userWalletId.stringValue, response)
|
||||
}
|
||||
|
||||
private suspend fun fetchUserMarketCoinsByIds(userWalletId: UserWalletId, userTokens: UserTokensResponse) {
|
||||
try {
|
||||
val response = tangemTechApi.getCoins(
|
||||
networkIds = userTokens.tokens.joinToString(separator = ",") { it.networkId },
|
||||
)
|
||||
userMarketCoinsStore.store(userWalletId, response)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e("Unable to fetch user market coins for: ${userWalletId.stringValue} ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFetchTokensErrorOrThrow(userWallet: UserWallet, error: Throwable) {
|
||||
val errorMessage = error.message ?: throw error
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.exchange
|
||||
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
|
||||
/**
|
||||
* Manager that holds info about available actions as Sell and Buy
|
||||
*/
|
||||
interface RampStateManager {
|
||||
fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean
|
||||
fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean
|
||||
}
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.tokens"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ sealed class CryptoCurrency : Serializable {
|
|||
|
||||
val rawCurrencyId: String? = (suffix as? Suffix.RawID)?.rawId
|
||||
|
||||
val rawNetworkId: String = networkId.value
|
||||
|
||||
/**
|
||||
* Represents the different types of prefixes that can be associated with a cryptocurrency ID.
|
||||
* These prefixes can help in quickly categorizing the type of cryptocurrency.
|
||||
|
|
|
|||
|
|
@ -1,34 +1,84 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
|
||||
/**
|
||||
* Use case to determine which TokenActions are available for a [CryptoCurrency]
|
||||
*
|
||||
* @property rampManager Ramp manager to check ramp availability
|
||||
*/
|
||||
class GetCryptoCurrencyActionsUseCase(
|
||||
private val rampManager: RampStateManager,
|
||||
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId, tokenId: String): Flow<TokenActionsState> {
|
||||
operator fun invoke(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Flow<TokenActionsState> {
|
||||
return flow {
|
||||
emit(getMockState(userWalletId, tokenId))
|
||||
val actionStates = createTokenActionsState(userWalletId, cryptoCurrency)
|
||||
emit(actionStates)
|
||||
}.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
// TODO replace by real data
|
||||
private fun getMockState(userWalletId: UserWalletId, tokenId: String): TokenActionsState {
|
||||
private suspend fun createTokenActionsState(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): TokenActionsState {
|
||||
return TokenActionsState(
|
||||
walletId = userWalletId,
|
||||
tokenId = tokenId,
|
||||
states = listOf(
|
||||
TokenActionsState.ActionState.Buy(true),
|
||||
TokenActionsState.ActionState.Send(true),
|
||||
TokenActionsState.ActionState.Receive(true),
|
||||
TokenActionsState.ActionState.Sell(true),
|
||||
TokenActionsState.ActionState.Swap(true),
|
||||
),
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
states = createListOfActions(userWalletId, cryptoCurrency),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates list of action for expected order
|
||||
* Actions priority: [Buy Send Receive Sell Swap]
|
||||
*/
|
||||
private suspend fun createListOfActions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
return buildList {
|
||||
// todo add check available in swap 1inch etc if backend doen't handle it
|
||||
if (marketCryptoCurrencyRepository.isExchangeable(userWalletId, cryptoCurrency.id) &&
|
||||
!isCustomToken(cryptoCurrency)
|
||||
) {
|
||||
addFirst(TokenActionsState.ActionState.Swap(true))
|
||||
} else {
|
||||
add(TokenActionsState.ActionState.Swap(false))
|
||||
}
|
||||
|
||||
if (rampManager.availableForSell(cryptoCurrency)) {
|
||||
addFirst(TokenActionsState.ActionState.Sell(true))
|
||||
} else {
|
||||
add(TokenActionsState.ActionState.Sell(false))
|
||||
}
|
||||
|
||||
addFirst(TokenActionsState.ActionState.Receive(true))
|
||||
addFirst(TokenActionsState.ActionState.Send(true))
|
||||
|
||||
if (rampManager.availableForBuy(cryptoCurrency)) {
|
||||
addFirst(TokenActionsState.ActionState.Buy(true))
|
||||
} else {
|
||||
add(TokenActionsState.ActionState.Buy(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCustomToken(currency: CryptoCurrency): Boolean {
|
||||
return currency is CryptoCurrency.Token && currency.isCustom
|
||||
}
|
||||
|
||||
private fun <T> MutableList<T>.addFirst(item: T) {
|
||||
this.add(0, item)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.domain.tokens.model
|
||||
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
data class TokenActionsState(
|
||||
val walletId: UserWalletId,
|
||||
val tokenId: String,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val states: List<ActionState>,
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* MarketCryptoCurrencyRepository works with data from Tangem coins backend, CoinMarketCap etc
|
||||
*/
|
||||
interface MarketCryptoCurrencyRepository {
|
||||
|
||||
suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Boolean
|
||||
}
|
||||
|
|
@ -84,12 +84,12 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
|
||||
private fun updateContent(selectedWallet: UserWallet) {
|
||||
updateMarketPrice(selectedWallet = selectedWallet)
|
||||
updateButtons(userWalletId = selectedWallet.walletId, currencyId = cryptoCurrency.id.value)
|
||||
updateButtons(userWalletId = selectedWallet.walletId, currency = cryptoCurrency)
|
||||
updateTxHistory()
|
||||
}
|
||||
|
||||
private fun updateButtons(userWalletId: UserWalletId, currencyId: String) {
|
||||
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, tokenId = currencyId)
|
||||
private fun updateButtons(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, cryptoCurrency = currency)
|
||||
.distinctUntilChanged()
|
||||
.onEach { uiState = stateFactory.getManageButtonsState(actions = it.states) }
|
||||
.flowOn(dispatchers.io)
|
||||
|
|
|
|||
|
|
@ -201,7 +201,6 @@ internal class WalletViewModel @Inject constructor(
|
|||
private fun updateSingleCurrencyContent(index: Int, isRefreshing: Boolean) {
|
||||
val wallet = getWallet(index)
|
||||
val blockchain = getCardTypeResolver(index).getBlockchain()
|
||||
updateButtons(userWalletId = wallet.walletId, currencyId = blockchain.id)
|
||||
updateTxHistory(
|
||||
blockchain = blockchain,
|
||||
derivationStyle = wallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
|
||||
|
|
@ -244,15 +243,18 @@ internal class WalletViewModel @Inject constructor(
|
|||
isRefreshing = isRefreshing,
|
||||
)
|
||||
|
||||
either.onRight { status -> cryptoCurrencyStatus = status }
|
||||
either.onRight { status ->
|
||||
cryptoCurrencyStatus = status
|
||||
updateButtons(userWalletId = userWalletId, currency = status.currency)
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(marketPriceJobHolder)
|
||||
}
|
||||
|
||||
private fun updateButtons(userWalletId: UserWalletId, currencyId: String) {
|
||||
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, tokenId = currencyId)
|
||||
private fun updateButtons(userWalletId: UserWalletId, currency: CryptoCurrency) {
|
||||
getCryptoCurrencyActionsUseCase(userWalletId = userWalletId, cryptoCurrency = currency)
|
||||
.distinctUntilChanged()
|
||||
.onEach { uiState = stateFactory.getSingleCurrencyManageButtonsState(actions = it.states) }
|
||||
.flowOn(dispatchers.io)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue