Updated on 2026-08-14
This commit is contained in:
parent
e2b5e5b07b
commit
81b2f9e4a3
10 changed files with 375 additions and 0 deletions
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object SwapDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAvailablePairsUseCase(swapRepository: SwapRepository): GetAvailablePairsUseCase {
|
||||
return GetAvailablePairsUseCase(swapRepository = swapRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@ android {
|
|||
dependencies {
|
||||
/** Project - API */
|
||||
implementation(projects.features.onramp.api)
|
||||
implementation(projects.features.swap.domain)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
|
||||
/** Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/** Token list component that present list of available tokens for swap */
|
||||
@Stable
|
||||
internal interface AvailableSwapPairsComponent : ComposableContentComponent {
|
||||
|
||||
/** Component factory */
|
||||
interface Factory : ComponentFactory<Params, AvailableSwapPairsComponent>
|
||||
|
||||
/**
|
||||
* Params
|
||||
*
|
||||
* @property userWalletId id of multi-currency wallet
|
||||
* @property selectedStatus flow of selected status
|
||||
* @property onTokenClick callback for token click
|
||||
*/
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val selectedStatus: StateFlow<CryptoCurrencyStatus?>,
|
||||
val onTokenClick: (TokenItemState, CryptoCurrencyStatus) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel
|
||||
import com.tangem.features.onramp.tokenlist.ui.TokenList
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Stable
|
||||
internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: AvailableSwapPairsComponent.Params,
|
||||
) : AvailableSwapPairsComponent, AppComponentContext by context {
|
||||
|
||||
private val model: AvailableSwapPairsModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
TokenList(state = state, modifier = modifier)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : AvailableSwapPairsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: AvailableSwapPairsComponent.Params,
|
||||
): DefaultAvailableSwapPairsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.di
|
||||
|
||||
import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent
|
||||
import com.tangem.features.onramp.swap.availablepairs.DefaultAvailableSwapPairsComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface AvailableSwapPairsComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAvailableSwapPairsComponentFactory(
|
||||
factory: DefaultAvailableSwapPairsComponent.Factory,
|
||||
): AvailableSwapPairsComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface AvailableSwapPairsModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AvailableSwapPairsModel::class)
|
||||
fun bindAvailableSwapPairsModel(model: AvailableSwapPairsModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.entity.converters
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Convert from [CryptoCurrencyStatus] to loading [TokensListItemUM.Token] state
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object LoadingTokenListItemConverter : Converter<CryptoCurrencyStatus, TokensListItemUM.Token> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokensListItemUM.Token {
|
||||
return TokensListItemUM.Token(
|
||||
state = TokenItemState.Loading(
|
||||
id = value.currency.id.value,
|
||||
iconState = CurrencyIconState.Loading,
|
||||
titleState = TokenItemState.TitleState.Loading,
|
||||
subtitleState = TokenItemState.SubtitleState.Loading,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.entity.transformers
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Set [statuses] as loading items
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SetLoadingTokenItemsTransformer(
|
||||
private val statuses: List<CryptoCurrencyStatus>,
|
||||
) : TokenListUMTransformer {
|
||||
|
||||
override fun transform(prevState: TokenListUM): TokenListUM {
|
||||
return prevState.copy(
|
||||
availableItems = LoadingTokenListItemConverter.convertList(input = statuses).toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.features.onramp.swap.availablepairs.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.utils.getOrElse
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenList
|
||||
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent
|
||||
import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetLoadingTokenItemsTransformer
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUM
|
||||
import com.tangem.features.onramp.tokenlist.entity.TokenListUMController
|
||||
import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateTokenItemsTransformer
|
||||
import com.tangem.features.onramp.utils.SearchManager
|
||||
import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer
|
||||
import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class AvailableSwapPairsModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
getTokenListUseCase: GetTokenListUseCase,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tokenListUMController: TokenListUMController,
|
||||
private val searchManager: SearchManager,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getAvailablePairsUseCase: GetAvailablePairsUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
||||
private var params: AvailableSwapPairsComponent.Params = paramsContainer.require()
|
||||
|
||||
private val tokenListFlow = getTokenListUseCase.launch(userWalletId = params.userWalletId)
|
||||
.distinctUntilChanged()
|
||||
.map { maybeTokenList ->
|
||||
maybeTokenList.getOrElse(
|
||||
ifLoading = { it ?: TokenList.Empty },
|
||||
ifError = { TokenList.Empty },
|
||||
)
|
||||
.flattenCurrencies()
|
||||
}
|
||||
.shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
private val availablePairsByNetworkFlow = MutableStateFlow<Map<LeastTokenInfo, List<SwapPairLeast>>>(emptyMap())
|
||||
|
||||
init {
|
||||
subscribeOnUpdateState()
|
||||
subscribeOnAvailablePairsUpdates()
|
||||
}
|
||||
|
||||
private fun subscribeOnUpdateState() {
|
||||
combine(
|
||||
flow = tokenListFlow,
|
||||
flow2 = getSelectedAppCurrencyUseCase().map { it.getOrElse { AppCurrency.Default } }.distinctUntilChanged(),
|
||||
flow3 = getBalanceHidingSettingsUseCase().map { it.isBalanceHidden }.distinctUntilChanged(),
|
||||
flow4 = searchManager.query,
|
||||
flow5 = availablePairsByNetworkFlow
|
||||
.map { it[params.selectedStatus.value?.toLeastTokenInfo()].orEmpty() }
|
||||
.distinctUntilChanged(),
|
||||
) { currencies, appCurrency, isBalanceHidden, query, availablePairs ->
|
||||
if (availablePairs.isEmpty()) {
|
||||
SetLoadingTokenItemsTransformer(currencies)
|
||||
} else {
|
||||
val filterTokenList = currencies
|
||||
.filterByQuery(query = query)
|
||||
.filterByAvailability(availablePairs = availablePairs)
|
||||
|
||||
Timber.e("${filterTokenList[true].orEmpty().size}")
|
||||
|
||||
UpdateTokenItemsTransformer(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = params.onTokenClick,
|
||||
statuses = filterTokenList,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
hasSearchBar = currencies.isNotEmpty(),
|
||||
onQueryChange = ::onSearchQueryChange,
|
||||
onActiveChange = ::onSearchBarActiveChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
.onEach(tokenListUMController::update)
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnAvailablePairsUpdates() {
|
||||
modelScope.launch {
|
||||
params.selectedStatus
|
||||
.filterNotNull()
|
||||
.collectLatest { selectedStatus ->
|
||||
val initialCurrency = selectedStatus.toLeastTokenInfo()
|
||||
val tokenList = tokenListFlow.firstOrNull() ?: return@collectLatest
|
||||
|
||||
val availablePairs = availablePairsByNetworkFlow.value[initialCurrency]
|
||||
if (!availablePairs.isNullOrEmpty()) return@collectLatest
|
||||
|
||||
val pairs = getAvailablePairsUseCase(
|
||||
initialCurrency = initialCurrency,
|
||||
currencies = tokenList.map(CryptoCurrencyStatus::currency),
|
||||
)
|
||||
|
||||
Timber.e("$initialCurrency = ${pairs.size}")
|
||||
|
||||
availablePairsByNetworkFlow.update {
|
||||
it.toMutableMap().apply {
|
||||
put(initialCurrency, pairs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchQueryChange(newQuery: String) {
|
||||
val searchBar = tokenListUMController.getSearchBar()
|
||||
if (searchBar?.searchBarUM?.query == newQuery) return
|
||||
|
||||
modelScope.launch {
|
||||
tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery))
|
||||
|
||||
searchManager.update(newQuery)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSearchBarActiveChange(isActive: Boolean) {
|
||||
tokenListUMController.update(
|
||||
transformer = UpdateSearchBarActiveStateTransformer(
|
||||
isActive = isActive,
|
||||
placeHolder = resourceReference(id = R.string.common_search),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByQuery(query: String): List<CryptoCurrencyStatus> {
|
||||
return filter {
|
||||
it.currency.name.contains(other = query, ignoreCase = true) ||
|
||||
it.currency.symbol.contains(other = query, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterByAvailability(
|
||||
availablePairs: List<SwapPairLeast>,
|
||||
): Map<Boolean, List<CryptoCurrencyStatus>> {
|
||||
return groupBy { status ->
|
||||
val isAvailable = availablePairs.map(SwapPairLeast::to).contains(status.toLeastTokenInfo())
|
||||
|
||||
isAvailable &&
|
||||
status.value !is CryptoCurrencyStatus.MissedDerivation &&
|
||||
status.value !is CryptoCurrencyStatus.Unreachable
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo {
|
||||
return LeastTokenInfo(
|
||||
contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0",
|
||||
network = currency.network.backendId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
|
||||
|
||||
class GetAvailablePairsUseCase(
|
||||
private val swapRepository: SwapRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): List<SwapPairLeast> {
|
||||
return swapRepository.getPairsOnly(initialCurrency, currencies).pairs
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue