Updated on 2026-08-14
This commit is contained in:
commit
c50840894f
739 changed files with 27664 additions and 4701 deletions
|
|
@ -12,7 +12,6 @@ import com.arkivanov.decompose.router.slot.childSlot
|
|||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.arkivanov.essenty.lifecycle.subscribe
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
|
|
@ -22,7 +21,6 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenComponent
|
||||
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
|
||||
import com.tangem.feature.swap.model.SwapModel
|
||||
|
|
@ -56,10 +54,6 @@ internal class DefaultSwapComponent @AssistedInject constructor(
|
|||
context = child("chooseTokenComponent"),
|
||||
params = ChooseTokenComponent.Params(
|
||||
bridge = model.chooseTokenBridge,
|
||||
settings = ChooseTokenComponent.Settings.SwapTo,
|
||||
analyticsPayload = setOf(
|
||||
ChooseTokenAnalyticsPayload.ScreensSources(ScreensSources.Swap.value),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
||||
internal class DefaultSwapFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : SwapFeatureToggles {
|
||||
override val isMarketListFeatureEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.SWAP_MARKET_LIST_ENABLED)
|
||||
}
|
||||
internal class DefaultSwapFeatureToggles : SwapFeatureToggles
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package com.tangem.feature.swap.choosetoken.api
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
// todo swap move to common-features module
|
||||
interface ChooseTokenBridge : ChooseTokenBridgeLegacy, ChooseTokenBridgeInternal {
|
||||
|
||||
val onCurrencyChosen: Channel<ChooseTokenResult>
|
||||
val onClose: Channel<Unit>
|
||||
|
||||
/**
|
||||
* for some Feature specific tokens filtering
|
||||
*/
|
||||
val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean>
|
||||
|
||||
fun selectWalletTab(walletId: UserWalletId)
|
||||
|
||||
data class Settings(
|
||||
val title: TextReference,
|
||||
val isShowMarketBlock: Boolean,
|
||||
val isShowPaymentAccount: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
val SwapFrom = Settings(
|
||||
title = resourceReference(R.string.swapping_from_title),
|
||||
isShowMarketBlock = false,
|
||||
isShowPaymentAccount = true,
|
||||
)
|
||||
val SwapTo = Settings(
|
||||
title = resourceReference(R.string.swapping_to_title),
|
||||
isShowMarketBlock = true,
|
||||
isShowPaymentAccount = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Factory {
|
||||
fun create(
|
||||
modelScope: CoroutineScope,
|
||||
settings: Settings,
|
||||
analyticsPayload: Set<ChooseTokenAnalyticsPayload> = emptySet(),
|
||||
): ChooseTokenBridge
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* primary for internal impl usage, but you can also use it externally
|
||||
*/
|
||||
interface ChooseTokenBridgeInternal {
|
||||
val settings: ChooseTokenBridge.Settings
|
||||
val analyticsPayload: Set<ChooseTokenAnalyticsPayload>
|
||||
val searchQueryState: StateFlow<SearchQuery>
|
||||
val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?>
|
||||
|
||||
fun onSearchQuery(query: SearchQuery)
|
||||
fun onSearchQuery(query: String) = onSearchQuery(SearchQuery(query))
|
||||
|
||||
fun onClose()
|
||||
fun onCurrencyChosen(result: ChooseTokenResult)
|
||||
|
||||
@JvmInline
|
||||
value class SearchQuery(val value: String) {
|
||||
companion object {
|
||||
val Empty = SearchQuery("")
|
||||
val SearchQuery.isSearchingState: Boolean get() = this.value.isNotBlank()
|
||||
val StateFlow<SearchQuery>.isSearchingState: Boolean get() = this.value.isSearchingState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo swap legacy api, remove
|
||||
interface ChooseTokenBridgeLegacy : ChooseTokenBridgeInternal {
|
||||
|
||||
val onTokenSelected: Channel<ChooseTokenResultOld>
|
||||
val onNewTokenAdded: Channel<Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>>
|
||||
|
||||
val currenciesGroup: Flow<CurrenciesGroup>
|
||||
|
||||
fun onTokenSelected(result: ChooseTokenResultOld) {
|
||||
onTokenSelected.trySend(result)
|
||||
onSearchQuery(SearchQuery.Empty)
|
||||
}
|
||||
|
||||
fun onNewTokenAdded(addedToken: Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>) {
|
||||
onNewTokenAdded.trySend(addedToken)
|
||||
onSearchQuery(SearchQuery.Empty)
|
||||
}
|
||||
|
||||
fun updateCurrenciesGroup(currenciesGroup: CurrenciesGroup)
|
||||
}
|
||||
|
||||
data class ChooseTokenResultOld(
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val account: Account,
|
||||
val isSearched: Boolean,
|
||||
)
|
||||
|
||||
data class ChooseTokenResult(
|
||||
val currency: CryptoCurrencyStatus,
|
||||
val account: AccountStatus,
|
||||
val wallet: UserWallet,
|
||||
val analyticsPayload: Set<ChooseTokenAnalyticsPayload> = emptySet(),
|
||||
) {
|
||||
val walletId get() = wallet.walletId
|
||||
}
|
||||
|
||||
sealed interface ChooseTokenAnalyticsPayload {
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
@JvmInline
|
||||
value class IsSearched(val value: Boolean) : ChooseTokenAnalyticsPayload
|
||||
|
||||
@JvmInline
|
||||
value class ScreensSources(val value: String) : ChooseTokenAnalyticsPayload
|
||||
}
|
||||
|
|
@ -2,103 +2,12 @@ package com.tangem.feature.swap.choosetoken.api
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
// todo swap make universal, encapsulate, move to some common module
|
||||
internal interface ChooseTokenBridge {
|
||||
|
||||
// todo swap new api
|
||||
val onCurrencyChosen: Channel<ChooseTokenResult>
|
||||
val onClose: Channel<Unit>
|
||||
|
||||
// todo swap legacy api, remove
|
||||
val onTokenSelected: Channel<Pair<String, ChooseTokenAnalyticsPayload.IsSearched>>
|
||||
val onNewTokenAdded: Channel<Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>>
|
||||
|
||||
val searchQueryState: StateFlow<String>
|
||||
val currenciesGroup: Flow<CurrenciesGroup>
|
||||
|
||||
fun onTokenSelected(tokenId: Pair<String, ChooseTokenAnalyticsPayload.IsSearched>) {
|
||||
onTokenSelected.trySend(tokenId)
|
||||
onSearchQuery("")
|
||||
}
|
||||
|
||||
fun onNewTokenAdded(addedToken: Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>) {
|
||||
onNewTokenAdded.trySend(addedToken)
|
||||
onSearchQuery("")
|
||||
}
|
||||
|
||||
fun onSearchQuery(query: String)
|
||||
|
||||
fun updateCurrenciesGroup(currenciesGroup: CurrenciesGroup)
|
||||
|
||||
fun onCurrencyChosen(result: ChooseTokenResult) {
|
||||
onCurrencyChosen.trySend(result)
|
||||
}
|
||||
|
||||
fun onClose() {
|
||||
onClose.trySend(Unit)
|
||||
onSearchQuery("")
|
||||
}
|
||||
|
||||
interface Factory {
|
||||
fun create(modelScope: CoroutineScope): ChooseTokenBridge
|
||||
}
|
||||
}
|
||||
|
||||
data class ChooseTokenResult(
|
||||
val currency: CryptoCurrencyStatus,
|
||||
val account: AccountStatus,
|
||||
val wallet: UserWallet,
|
||||
val analyticsPayload: Set<ChooseTokenAnalyticsPayload> = emptySet(),
|
||||
) {
|
||||
val walletId get() = wallet.walletId
|
||||
}
|
||||
|
||||
sealed interface ChooseTokenAnalyticsPayload {
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
@JvmInline
|
||||
value class IsSearched(val value: Boolean) : ChooseTokenAnalyticsPayload
|
||||
|
||||
@JvmInline
|
||||
value class ScreensSources(val value: String) : ChooseTokenAnalyticsPayload
|
||||
}
|
||||
|
||||
internal interface ChooseTokenComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val bridge: ChooseTokenBridge,
|
||||
val settings: Settings,
|
||||
val analyticsPayload: Set<ChooseTokenAnalyticsPayload> = emptySet(),
|
||||
)
|
||||
|
||||
data class Settings(
|
||||
val title: TextReference,
|
||||
val isShowMarketBlock: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
val SwapFrom = Settings(
|
||||
title = resourceReference(R.string.swapping_from_title),
|
||||
isShowMarketBlock = false,
|
||||
)
|
||||
val SwapTo = Settings(
|
||||
title = resourceReference(R.string.swapping_to_title),
|
||||
isShowMarketBlock = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, ChooseTokenComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.feature.swap.choosetoken.api.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
data class ChooseTokenPortfolioFullBlockUM(
|
||||
val walletList: WalletListUM,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isSearching: Boolean,
|
||||
val tokensListData: TokenListUMData,
|
||||
)
|
||||
|
||||
data class WalletListUM(
|
||||
val items: ImmutableList<WalletTabUM>,
|
||||
)
|
||||
|
||||
data class WalletTabUM(
|
||||
val text: TextReference,
|
||||
val count: TextReference?,
|
||||
val isSelected: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
sealed interface TokenListUMData {
|
||||
|
||||
val tokensList: ImmutableList<TokensListItemUM>
|
||||
val totalTokensCount: Int
|
||||
|
||||
data class AccountList(
|
||||
override val tokensList: ImmutableList<TokensListItemUM.Portfolio>,
|
||||
override val totalTokensCount: Int,
|
||||
) : TokenListUMData
|
||||
|
||||
data class TokenList(
|
||||
override val tokensList: ImmutableList<TokensListItemUM>,
|
||||
override val totalTokensCount: Int,
|
||||
) : TokenListUMData
|
||||
|
||||
data object EmptyList : TokenListUMData {
|
||||
override val tokensList: ImmutableList<TokensListItemUM> = persistentListOf()
|
||||
override val totalTokensCount: Int = EMPTY_TOKENS_COUNT
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EMPTY_TOKENS_COUNT = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,19 @@
|
|||
package com.tangem.feature.swap.choosetoken.impl
|
||||
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge.Settings
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResult
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResultOld
|
||||
import com.tangem.feature.swap.choosetoken.api.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.ChooseTokenModel.Companion.DEBOUNCE_SEARCH_DELAY
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.PortfolioFullBlockDelegate
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.PortfolioListBlockDelegate
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -15,32 +24,78 @@ import kotlinx.coroutines.flow.*
|
|||
|
||||
internal class DefaultChooseTokenBridge @AssistedInject constructor(
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
portfolioListBlockDelegateFactory: PortfolioListBlockDelegate.Factory,
|
||||
portfolioFullBlockDelegateFactory: PortfolioFullBlockDelegate.Factory,
|
||||
@Assisted override val settings: Settings,
|
||||
@Assisted override val analyticsPayload: Set<ChooseTokenAnalyticsPayload>,
|
||||
) : ChooseTokenBridge {
|
||||
|
||||
override val onCurrencyChosen: Channel<ChooseTokenResult> = Channel()
|
||||
|
||||
override val onTokenSelected: Channel<Pair<String, ChooseTokenAnalyticsPayload.IsSearched>> = Channel()
|
||||
override val onTokenSelected: Channel<ChooseTokenResultOld> = Channel()
|
||||
override val onNewTokenAdded: Channel<Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>> = Channel()
|
||||
override val onClose: Channel<Unit> = Channel()
|
||||
|
||||
private val onSearchQuery: Channel<String> = Channel()
|
||||
override val searchQueryState: StateFlow<String> = onSearchQuery.receiveAsFlow()
|
||||
private val onSearchQuery: Channel<SearchQuery> = Channel()
|
||||
override val searchQueryState: StateFlow<SearchQuery> = onSearchQuery.receiveAsFlow()
|
||||
.debounce(DEBOUNCE_SEARCH_DELAY)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = "")
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = SearchQuery.Empty)
|
||||
|
||||
private val portfolioListBlockDelegate: PortfolioListBlockDelegate = portfolioListBlockDelegateFactory.create(
|
||||
modelScope = modelScope,
|
||||
searchQueryState = searchQueryState,
|
||||
featureSettings = settings,
|
||||
)
|
||||
|
||||
private val portfolioFullBlockDelegate: PortfolioFullBlockDelegate = portfolioFullBlockDelegateFactory.create(
|
||||
modelScope = modelScope,
|
||||
searchQueryState = searchQueryState,
|
||||
portfolioListBlockDelegate = portfolioListBlockDelegate,
|
||||
)
|
||||
|
||||
override val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean>
|
||||
get() = portfolioListBlockDelegate.tokenFilter
|
||||
|
||||
override val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?>
|
||||
get() = portfolioFullBlockDelegate.fullPortfolioBlock
|
||||
|
||||
private val _currenciesGroupFlow = MutableStateFlow<CurrenciesGroup?>(null)
|
||||
override val currenciesGroup: Flow<CurrenciesGroup> = _currenciesGroupFlow.filterNotNull()
|
||||
|
||||
override fun onSearchQuery(query: String) {
|
||||
init {
|
||||
portfolioListBlockDelegate.onTokenChosen.receiveAsFlow()
|
||||
.onEach { chooseResult -> onCurrencyChosen(chooseResult) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
override fun selectWalletTab(walletId: UserWalletId) {
|
||||
portfolioFullBlockDelegate.selectWalletTab(walletId)
|
||||
}
|
||||
|
||||
override fun onSearchQuery(query: SearchQuery) {
|
||||
onSearchQuery.trySend(query)
|
||||
}
|
||||
|
||||
override fun onCurrencyChosen(result: ChooseTokenResult) {
|
||||
onCurrencyChosen.trySend(result)
|
||||
onSearchQuery(SearchQuery.Empty)
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
onClose.trySend(Unit)
|
||||
onSearchQuery(SearchQuery.Empty)
|
||||
}
|
||||
|
||||
override fun updateCurrenciesGroup(currenciesGroup: CurrenciesGroup) {
|
||||
_currenciesGroupFlow.update { currenciesGroup }
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : ChooseTokenBridge.Factory {
|
||||
override fun create(modelScope: CoroutineScope): DefaultChooseTokenBridge
|
||||
override fun create(
|
||||
modelScope: CoroutineScope,
|
||||
settings: Settings,
|
||||
analyticsPayload: Set<ChooseTokenAnalyticsPayload>,
|
||||
): DefaultChooseTokenBridge
|
||||
}
|
||||
}
|
||||
|
|
@ -15,15 +15,15 @@ import com.tangem.feature.swap.choosetoken.api.ChooseTokenComponent
|
|||
import com.tangem.feature.swap.choosetoken.impl.model.ChooseTokenModel
|
||||
import com.tangem.feature.swap.models.AddToPortfolioRoute
|
||||
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultChooseTokenComponent @AssistedInject constructor(
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: ChooseTokenComponent.Params,
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, ChooseTokenComponent {
|
||||
|
||||
private val model: ChooseTokenModel = getOrCreateModel(params)
|
||||
|
|
@ -54,9 +54,7 @@ internal class DefaultChooseTokenComponent @AssistedInject constructor(
|
|||
return addToPortfolioComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AddToPortfolioComponent.Params(
|
||||
addToPortfolioManager = model.addToPortfolioManager!!,
|
||||
callback = model.addToPortfolioCallback,
|
||||
shouldSkipTokenActionsScreen = true,
|
||||
addToPortfolioManager = model.addToPortfolioManager,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,41 @@ package com.tangem.feature.swap.choosetoken.impl.converter
|
|||
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.common.ui.account.TokensListPortfolioItemConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.common.ui.tokens.TokenItemGrouping.toGroupedItems
|
||||
import com.tangem.common.ui.tokens.TokenItemGrouping.toUngroupedItems
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.icons.IconTint
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.ClickIntents
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.isSearchingState
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class ChooseTokenListItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val params: TokenConverterParams,
|
||||
private val clickIntents: ClickIntents,
|
||||
private val searchQuery: String,
|
||||
private val searchQuery: SearchQuery,
|
||||
private val tokenFilter: (AccountStatus, CryptoCurrencyStatus) -> Boolean,
|
||||
private val isShowPaymentAccount: Boolean,
|
||||
) {
|
||||
|
||||
private val isSearchingState: Boolean get() = searchQuery.isSearchingState
|
||||
|
|
@ -33,6 +46,24 @@ internal class ChooseTokenListItemConverter(
|
|||
clickIntents.onTokenItemClick(account, currencyStatus)
|
||||
}
|
||||
|
||||
private val onAccountItemClick: (Account, isExpanded: Boolean) -> Unit = { clickedAccount, isExpanded ->
|
||||
if (isExpanded) {
|
||||
clickIntents.onAccountCollapseClick(clickedAccount)
|
||||
} else {
|
||||
clickIntents.onAccountExpandClick(clickedAccount)
|
||||
}
|
||||
}
|
||||
|
||||
private val fiatAmountStateProvider: ((TotalFiatBalance, isExpanded: Boolean) -> FiatAmountState?) =
|
||||
{ totalBalance, isExpanded ->
|
||||
when {
|
||||
isSearchingState -> FiatAmountState.Empty
|
||||
!isExpanded -> FiatAmountState.Icon(R.drawable.ic_chewron_down_20, IconTint.Informative)
|
||||
else -> AccountCryptoPortfolioItemStateConverter
|
||||
.createFiatAmountState(totalBalance, appCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tokenStatusConverter(account: AccountStatus) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
onItemClick = { _, status -> onTokenClick(account, status) },
|
||||
|
|
@ -42,6 +73,7 @@ internal class ChooseTokenListItemConverter(
|
|||
return when (params) {
|
||||
is TokenConverterParams.Account -> convertAccountList(params)
|
||||
is TokenConverterParams.Wallet -> convertTokenList(
|
||||
account = params.mainAccount,
|
||||
tokenConverter = tokenStatusConverter(params.mainAccount),
|
||||
tokenListParam = params.tokenList,
|
||||
)
|
||||
|
|
@ -51,16 +83,19 @@ internal class ChooseTokenListItemConverter(
|
|||
private fun convertAccountList(params: TokenConverterParams.Account): TokenListUMData {
|
||||
val accountList = params.accountList
|
||||
val accountItems = accountList.accountStatuses
|
||||
.filterCryptoPortfolio()
|
||||
.map { accountStatus -> accountStatus.toPortfolioItem(params) }
|
||||
.mapNotNull { accountStatus ->
|
||||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.toPortfolioItem(params)
|
||||
is AccountStatus.Payment -> accountStatus.createPaymentAccountItem(params.expandedAccounts)
|
||||
}
|
||||
}
|
||||
.filter { portfolio -> portfolio.tokens.isNotEmpty() }
|
||||
if (accountItems.isEmpty()) {
|
||||
return TokenListUMData.EmptyList
|
||||
}
|
||||
val accountsList = accountItems.toPersistentList()
|
||||
return TokenListUMData.AccountList(
|
||||
tokensList = accountsList,
|
||||
totalTokensCount = accountsList.size,
|
||||
tokensList = accountItems.toPersistentList(),
|
||||
totalTokensCount = accountItems.sumOf { portfolio -> portfolio.tokensItemsList.size },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -71,21 +106,20 @@ internal class ChooseTokenListItemConverter(
|
|||
val account: Account.CryptoPortfolio = this.account
|
||||
val isExpanded = isSearchingState || params.expandedAccounts.contains(account.accountId)
|
||||
val onItemClick: (Account.CryptoPortfolio) -> Unit = { clickedAccount ->
|
||||
if (isExpanded) {
|
||||
clickIntents.onAccountCollapseClick(clickedAccount)
|
||||
} else {
|
||||
clickIntents.onAccountExpandClick(clickedAccount)
|
||||
}
|
||||
onAccountItemClick(clickedAccount, isExpanded)
|
||||
}
|
||||
|
||||
val converter = AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = account,
|
||||
onItemClick = onItemClick.takeIf { !isSearchingState },
|
||||
priceChangeLce = this.priceChangeLce,
|
||||
fiatAmountStateProvider = { fiatBalance -> fiatAmountStateProvider(fiatBalance, isExpanded) },
|
||||
subtitle2StateProvider = { _ -> null },
|
||||
)
|
||||
val accountItem = converter.convert(tokenList.totalFiatBalance)
|
||||
val tokenConverter = tokenStatusConverter(this)
|
||||
val tokensListState = convertTokenList(tokenConverter, tokenList)
|
||||
val tokensListState = convertTokenList(tokenConverter, tokenList, this)
|
||||
val items = tokensListState.tokensList
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = accountItem,
|
||||
|
|
@ -95,41 +129,38 @@ internal class ChooseTokenListItemConverter(
|
|||
).convert(Unit)
|
||||
}
|
||||
|
||||
private fun convertTokenList(tokenConverter: TokenItemStateConverter, tokenListParam: TokenList): TokenListUMData {
|
||||
val tokenList = if (isSearchingState) filterByQuery(tokenListParam) else tokenListParam
|
||||
|
||||
return when (tokenList) {
|
||||
private fun convertTokenList(
|
||||
tokenConverter: TokenItemStateConverter,
|
||||
tokenListParam: TokenList,
|
||||
account: AccountStatus.CryptoPortfolio,
|
||||
): TokenListUMData {
|
||||
return when (val tokenList = filterTokenList(tokenListParam, account)) {
|
||||
is TokenList.Empty -> TokenListUMData.EmptyList
|
||||
is TokenList.GroupedByNetwork -> tokenList.toGroupedItems(tokenConverter).let { grouped ->
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = grouped.toPersistentList(),
|
||||
totalTokensCount = grouped.size,
|
||||
)
|
||||
}
|
||||
is TokenList.Ungrouped -> tokenList.toUngroupedItems(tokenConverter).let { ungrouped ->
|
||||
TokenListUMData.TokenList(
|
||||
tokensList = ungrouped.toPersistentList(),
|
||||
totalTokensCount = ungrouped.size,
|
||||
)
|
||||
}
|
||||
is TokenList.GroupedByNetwork -> TokenListUMData.TokenList(
|
||||
tokensList = tokenList.toGroupedItems(tokenConverter).toPersistentList(),
|
||||
totalTokensCount = tokenList.flattenCurrencies().size,
|
||||
)
|
||||
is TokenList.Ungrouped -> TokenListUMData.TokenList(
|
||||
tokensList = tokenList.toUngroupedItems(tokenConverter).toPersistentList(),
|
||||
totalTokensCount = tokenList.flattenCurrencies().size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterByQuery(tokenList: TokenList): TokenList {
|
||||
fun List<CryptoCurrencyStatus>.filterByQuery(): List<CryptoCurrencyStatus> = filter { currency ->
|
||||
currency.currency.name.contains(searchQuery, ignoreCase = true) ||
|
||||
currency.currency.symbol.contains(searchQuery, ignoreCase = true)
|
||||
}
|
||||
private fun List<CryptoCurrencyStatus>.filterCurrencies(account: AccountStatus): List<CryptoCurrencyStatus> =
|
||||
filter { currency -> currency.filterByQuery() && tokenFilter(account, currency) }
|
||||
|
||||
private fun filterTokenList(tokenList: TokenList, account: AccountStatus.CryptoPortfolio): TokenList {
|
||||
return when (tokenList) {
|
||||
TokenList.Empty -> TokenList.Empty
|
||||
is TokenList.Ungrouped -> {
|
||||
val filtered = tokenList.currencies.filterByQuery()
|
||||
val filtered = tokenList.currencies.filterCurrencies(account)
|
||||
if (filtered.isEmpty()) TokenList.Empty else tokenList.copy(currencies = filtered)
|
||||
}
|
||||
is TokenList.GroupedByNetwork -> {
|
||||
val filteredGroups = tokenList.groups
|
||||
.map { group ->
|
||||
val filteredCurrencies = group.currencies.filterByQuery()
|
||||
val filteredCurrencies = group.currencies.filterCurrencies(account)
|
||||
group.copy(currencies = filteredCurrencies)
|
||||
}
|
||||
.filter { group -> group.currencies.isNotEmpty() }
|
||||
|
|
@ -137,4 +168,63 @@ internal class ChooseTokenListItemConverter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.filterByQuery(): Boolean {
|
||||
if (!isSearchingState) return true
|
||||
val isSearchFilter = currency.name.contains(searchQuery.value, ignoreCase = true) ||
|
||||
currency.symbol.contains(searchQuery.value, ignoreCase = true)
|
||||
return isSearchFilter
|
||||
}
|
||||
|
||||
private fun AccountStatus.Payment.createPaymentAccountItem(
|
||||
expandedAccounts: Set<AccountId>,
|
||||
): TokensListItemUM.Portfolio? {
|
||||
if (!isShowPaymentAccount) return null
|
||||
val paymentCurrency: CryptoCurrencyStatus = when (val status = this.value) {
|
||||
is PaymentAccountStatusValue.Error,
|
||||
is PaymentAccountStatusValue.IssuingCard,
|
||||
PaymentAccountStatusValue.NotCreated,
|
||||
is PaymentAccountStatusValue.UnderReview,
|
||||
PaymentAccountStatusValue.Loading,
|
||||
-> return null
|
||||
is PaymentAccountStatusValue.Loaded -> status.cryptoCurrencyStatus
|
||||
is PaymentAccountStatusValue.Locked -> status.cryptoCurrencyStatus
|
||||
}
|
||||
val account = this.account
|
||||
val tokensCount = 1
|
||||
val isExpanded = isSearchingState || expandedAccounts.contains(account.accountId)
|
||||
val onItemClick: (TokenItemState) -> Unit = {
|
||||
onAccountItemClick(account, isExpanded)
|
||||
}
|
||||
val fiatBalance: TotalFiatBalance = this.value.totalFiatBalance
|
||||
val fiatAmountState = fiatAmountStateProvider(fiatBalance, isExpanded)
|
||||
val paymentAccountItem = TokenItemState.Content(
|
||||
id = account.accountId.value,
|
||||
iconState = CurrencyIconState.PaymentAccount(),
|
||||
titleState = TokenItemState.TitleState.Content(text = account.accountName.toUM().value),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = pluralReference(
|
||||
R.plurals.common_tokens_count,
|
||||
count = tokensCount,
|
||||
formatArgs = wrappedList(tokensCount),
|
||||
),
|
||||
isAvailable = false,
|
||||
),
|
||||
onItemClick = onItemClick.takeIf { !isSearchingState },
|
||||
fiatAmountState = fiatAmountState,
|
||||
subtitle2State = null,
|
||||
onItemLongClick = null,
|
||||
)
|
||||
val tokenConverter = tokenStatusConverter(this)
|
||||
val filtered = listOf(paymentCurrency)
|
||||
.filterCurrencies(this)
|
||||
.map { currency -> TokensListItemUM.Token(tokenConverter.convert(currency)) }
|
||||
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = paymentAccountItem,
|
||||
isExpanded = isExpanded,
|
||||
isCollapsable = !isSearchingState,
|
||||
tokens = filtered.toPersistentList(),
|
||||
).convert(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,47 +5,28 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.*
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.feature.swap.choosetoken.impl.converter.SearchBarToggleTransformer
|
||||
import com.tangem.feature.swap.choosetoken.impl.converter.SearchBarUpdateQueryTransformer
|
||||
import com.tangem.feature.swap.choosetoken.impl.ui.ChooseTokenFullUM
|
||||
import com.tangem.feature.swap.choosetoken.impl.ui.ChooseTokenInitialUM
|
||||
import com.tangem.feature.swap.choosetoken.impl.ui.ChooseTokenUM
|
||||
import com.tangem.feature.swap.choosetoken.impl.ui.WalletListUM
|
||||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal val String.isSearchingState: Boolean get() = this.isNotBlank()
|
||||
internal val StateFlow<String>.isSearchingState: Boolean get() = this.value.isSearchingState
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class ChooseTokenModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val settingContextUseCase: SettingContextUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
portfolioListBlockDelegateFactory: PortfolioListBlockDelegate.Factory,
|
||||
marketBlockDelegateFactory: MarketBlockDelegate.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
|
@ -53,61 +34,71 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
private val params = paramsContainer.require<ChooseTokenComponent.Params>()
|
||||
private val bridge: ChooseTokenBridge = params.bridge
|
||||
|
||||
private val searchQueryState: StateFlow<String> = bridge.searchQueryState
|
||||
private val searchQueryState: StateFlow<SearchQuery> = bridge.searchQueryState
|
||||
private val isSearchingState: Boolean get() = bridge.searchQueryState.isSearchingState
|
||||
private val marketBlockDelegate: MarketBlockDelegate = marketBlockDelegateFactory.create(
|
||||
modelScope = modelScope,
|
||||
searchQueryState = searchQueryState,
|
||||
screensSourcesName = params.analyticsPayload
|
||||
screensSourcesName = bridge.analyticsPayload
|
||||
.filterIsInstance<ChooseTokenAnalyticsPayload.ScreensSources>()
|
||||
.firstOrNull()?.value.orEmpty(),
|
||||
)
|
||||
private val portfolioListBlockDelegate: PortfolioListBlockDelegate = portfolioListBlockDelegateFactory.create(
|
||||
modelScope = modelScope,
|
||||
searchQueryState = searchQueryState,
|
||||
)
|
||||
|
||||
val bottomSheetNavigation get() = marketBlockDelegate.addToPortfolioSlot
|
||||
val addToPortfolioManager get() = marketBlockDelegate.addToPortfolioManager
|
||||
private val marketsStateFlow: Flow<SwapMarketState?> = if (params.settings.isShowMarketBlock) {
|
||||
private val marketsStateFlow: Flow<SwapMarketState?> = if (bridge.settings.isShowMarketBlock) {
|
||||
marketBlockDelegate.marketsStateFlow
|
||||
} else {
|
||||
flowOf(null)
|
||||
}
|
||||
|
||||
private val expandedAccountsFlow: MutableStateFlow<Map<AccountId, Boolean>> = MutableStateFlow(emptyMap())
|
||||
private val onWalletSelected = Channel<UserWalletId>()
|
||||
|
||||
// todo swap call new api result
|
||||
val addToPortfolioCallback = object : AddToPortfolioComponent.Callback {
|
||||
override fun onDismiss() = marketBlockDelegate.addToPortfolioSlot.dismiss()
|
||||
|
||||
override fun onSuccess(addedToken: CryptoCurrency) {
|
||||
val newToken = addedToken to ChooseTokenAnalyticsPayload.IsSearched(isSearchingState)
|
||||
bridge.onNewTokenAdded(newToken)
|
||||
marketBlockDelegate.addToPortfolioSlot.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val stateOld: StateFlow<SwapSelectTokenStateHolder?> = combineUIOld()
|
||||
|
||||
private val contentState: StateFlow<ChooseTokenUM?> = combineUI()
|
||||
private val initialState: MutableStateFlow<ChooseTokenInitialUM> = MutableStateFlow(getInitState())
|
||||
val state: StateFlow<ChooseTokenFullUM> = combine(
|
||||
flow = initialState,
|
||||
flow2 = contentState,
|
||||
transform = { initial, content ->
|
||||
flow2 = bridge.fullPortfolioBlock,
|
||||
flow3 = marketsStateFlow,
|
||||
transform = { initial, content, marketBlock ->
|
||||
ChooseTokenFullUM(
|
||||
initialUM = initial,
|
||||
contentUM = content,
|
||||
portfolioBlock = content,
|
||||
marketsBlock = marketBlock,
|
||||
)
|
||||
},
|
||||
).stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = ChooseTokenFullUM(initialState.value, contentState.value),
|
||||
initialValue = ChooseTokenFullUM(
|
||||
initialUM = initialState.value,
|
||||
portfolioBlock = bridge.fullPortfolioBlock.value,
|
||||
marketsBlock = null,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
addToPortfolioManager.onDismiss.receiveAsFlow()
|
||||
.onEach { marketBlockDelegate.addToPortfolioSlot.dismiss() }
|
||||
.launchIn(modelScope)
|
||||
addToPortfolioManager.onSuccessAdded.receiveAsFlow()
|
||||
.onEach { addedResult ->
|
||||
val isSearched = ChooseTokenAnalyticsPayload.IsSearched(isSearchingState)
|
||||
val newToken = addedResult.addedCurrency.currency to isSearched
|
||||
bridge.onNewTokenAdded(newToken)
|
||||
val chooseTokenResult = ChooseTokenResult(
|
||||
currency = addedResult.addedCurrency,
|
||||
account = addedResult.account,
|
||||
wallet = addedResult.wallet,
|
||||
analyticsPayload = setOf(isSearched),
|
||||
)
|
||||
bridge.onCurrencyChosen(chooseTokenResult)
|
||||
marketBlockDelegate.addToPortfolioSlot.dismiss()
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun combineUIOld(): StateFlow<SwapSelectTokenStateHolder?> = combine(
|
||||
flow = bridge.currenciesGroup,
|
||||
|
|
@ -121,10 +112,13 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
|
||||
TokensDataConverter(
|
||||
onSearchEntered = { query -> bridge.onSearchQuery(query) },
|
||||
onTokenClick = { tokenId ->
|
||||
val selected = tokenId to ChooseTokenAnalyticsPayload
|
||||
.IsSearched(isSearchingState)
|
||||
bridge.onTokenSelected(selected)
|
||||
onTokenClick = { account, cryptoCurrencyStatus ->
|
||||
val result = ChooseTokenResultOld(
|
||||
account = account,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
isSearched = searchQueryState.isSearchingState,
|
||||
)
|
||||
bridge.onTokenSelected(result)
|
||||
},
|
||||
onAccountClick = { account ->
|
||||
expandedAccountsFlow.update { expandedList ->
|
||||
|
|
@ -145,94 +139,6 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
.flowOn(dispatchers.default)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun combineUI(): StateFlow<ChooseTokenUM?> = channelFlow {
|
||||
val allWalletsFlow: StateFlow<LinkedHashMap<UserWalletId, UserWallet>> =
|
||||
getWalletsUseCase.invokeAsMap().stateIn(this)
|
||||
|
||||
val selectedWalletFlow: StateFlow<UserWallet> =
|
||||
onWalletSelected.receiveAsFlow()
|
||||
.mapNotNull { walletId -> allWalletsFlow.value[walletId] }
|
||||
.stateIn(this, SharingStarted.Eagerly, allWalletsFlow.value.values.first())
|
||||
|
||||
val selectedWalletTokensData: Flow<TokenListUMData> = combine(
|
||||
flow = selectedWalletFlow.map { wallet -> wallet.walletId }.distinctUntilChanged(),
|
||||
flow2 = portfolioListBlockDelegate.portfolioList,
|
||||
transform = { selectedWalletId, allPortfoliosData -> allPortfoliosData[selectedWalletId] },
|
||||
)
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
|
||||
val walletListUmFlow = combine(
|
||||
flow = selectedWalletFlow,
|
||||
flow2 = allWalletsFlow,
|
||||
transform = { selectedWallet, allWallets ->
|
||||
allWallets.entries
|
||||
.map { (walletId, wallet) ->
|
||||
val type = if (selectedWallet.walletId == walletId) {
|
||||
TangemButtonType.Primary
|
||||
} else {
|
||||
TangemButtonType.Secondary
|
||||
}
|
||||
TangemButtonUM(
|
||||
text = stringReference(wallet.name),
|
||||
onClick = { onWalletSelected.trySend(walletId) },
|
||||
type = type,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
|
||||
portfolioListBlockDelegate.onTokenItemClick.receiveAsFlow()
|
||||
.onEach { (account, currencyStatus) ->
|
||||
onTokenItemClick(
|
||||
wallet = allWalletsFlow.value[account.accountId.userWalletId] ?: return@onEach,
|
||||
account = account,
|
||||
currencyStatus = currencyStatus,
|
||||
)
|
||||
}
|
||||
.launchIn(this)
|
||||
|
||||
combine(
|
||||
flow = selectedWalletTokensData,
|
||||
flow2 = settingContextUseCase.invoke(),
|
||||
flow3 = marketsStateFlow,
|
||||
flow4 = walletListUmFlow,
|
||||
transform = { tokensData, settings, marketsData, walletList ->
|
||||
val walletsUM = if (walletList.size != 1) {
|
||||
WalletListUM(walletList.toPersistentList())
|
||||
} else {
|
||||
WalletListUM(persistentListOf())
|
||||
}
|
||||
ChooseTokenUM(
|
||||
walletList = walletsUM,
|
||||
isBalanceHidden = settings.isBalanceHidden,
|
||||
isSearching = isSearchingState,
|
||||
tokensListData = tokensData,
|
||||
marketsState = marketsData,
|
||||
)
|
||||
},
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.collect { newUM -> channel.send(newUM) }
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
private fun onTokenItemClick(wallet: UserWallet, account: AccountStatus, currencyStatus: CryptoCurrencyStatus) {
|
||||
val analyticsPayload = setOf(
|
||||
ChooseTokenAnalyticsPayload.IsSearched(isSearchingState),
|
||||
)
|
||||
val result = ChooseTokenResult(
|
||||
account = account,
|
||||
currency = currencyStatus,
|
||||
wallet = wallet,
|
||||
analyticsPayload = analyticsPayload,
|
||||
)
|
||||
bridge.onCurrencyChosen(result)
|
||||
}
|
||||
|
||||
fun onBackClicked() {
|
||||
bridge.onClose()
|
||||
}
|
||||
|
|
@ -251,7 +157,7 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
)
|
||||
|
||||
private fun getInitState() = ChooseTokenInitialUM(
|
||||
screenTitle = params.settings.title,
|
||||
screenTitle = bridge.settings.title,
|
||||
onCloseClick = ::onBackClicked,
|
||||
searchBar = getInitialSearchBar(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,42 +14,44 @@ import com.tangem.domain.markets.toSerializableParam
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.feature.swap.models.AddToPortfolioRoute
|
||||
import com.tangem.feature.swap.models.market.MarketsListBatchFlowManager
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class MarketBlockDelegate @AssistedInject constructor(
|
||||
private val marketsListBatchFlowManagerFactory: MarketsListBatchFlowManager.Factory,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val getUserWalletsUseCase: GetWalletsUseCase,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
@Assisted private val searchQueryState: StateFlow<String>,
|
||||
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
|
||||
@Assisted private val screensSourcesName: String,
|
||||
) {
|
||||
|
||||
private val addToPortfolioJobHolder = JobHolder()
|
||||
private val visibleMarketItemIds = MutableStateFlow<List<CryptoCurrency.RawID>>(emptyList())
|
||||
private val visibleDefaultMarketItemIds = MutableStateFlow<List<CryptoCurrency.RawID>>(emptyList())
|
||||
|
||||
val addToPortfolioSlot: SlotNavigation<AddToPortfolioRoute> = SlotNavigation()
|
||||
var addToPortfolioManager: AddToPortfolioManager? = null
|
||||
val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory.create(
|
||||
scope = modelScope,
|
||||
settings = AddToPortfolioManager.Settings.ChooseToken,
|
||||
analyticsParams = AddToPortfolioManager.AnalyticsParams(source = screensSourcesName),
|
||||
)
|
||||
|
||||
val marketsStateFlow: Flow<SwapMarketState> = searchQueryState
|
||||
// Switch between default and search market flows
|
||||
.map { it.isEmpty() }
|
||||
.map { it.value.isEmpty() }
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { isDefaultMode ->
|
||||
if (isDefaultMode) {
|
||||
|
|
@ -74,7 +76,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
marketsListBatchFlowManagerFactory.create(
|
||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Search,
|
||||
order = TokenMarketListConfig.Order.ByRating,
|
||||
currentSearchText = Provider { searchQueryState.value },
|
||||
currentSearchText = Provider { searchQueryState.value.value },
|
||||
modelScope = modelScope,
|
||||
)
|
||||
}
|
||||
|
|
@ -83,8 +85,8 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
// Reload search markets when query changes
|
||||
searchQueryState
|
||||
.onEach { searchQuery ->
|
||||
if (searchQuery.isNotEmpty()) {
|
||||
searchMarketsListManager.reload(searchQuery)
|
||||
if (searchQuery.isSearchingState) {
|
||||
searchMarketsListManager.reload(searchQuery.value)
|
||||
}
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -157,7 +159,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
) { uiItems, isError, isSearchNotFound, total ->
|
||||
when {
|
||||
isError -> SwapMarketState.LoadingError(
|
||||
onRetryClicked = { searchMarketsListManager.reload(searchQueryState.value) },
|
||||
onRetryClicked = { searchMarketsListManager.reload(searchQueryState.value.value) },
|
||||
marketsTitle = marketsTitle,
|
||||
shouldAssetsCount = true,
|
||||
)
|
||||
|
|
@ -177,51 +179,40 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun addToPortfolioItem(item: MarketsListItemUM) {
|
||||
modelScope.launch {
|
||||
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
||||
?: searchMarketsListManager.getTokenMarketById(item.id)
|
||||
?: return@launch
|
||||
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
||||
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return
|
||||
|
||||
val param = tokenMarket.toSerializableParam()
|
||||
val hasOnlyHotWallets = getUserWalletsUseCase.invokeSync().all { it is UserWallet.Hot }
|
||||
val param = tokenMarket.toSerializableParam()
|
||||
val hasOnlyHotWallets = getUserWalletsUseCase.invokeSync().all { it is UserWallet.Hot }
|
||||
|
||||
val networks = tokenMarket.networks?.filter { network ->
|
||||
BlockchainUtils.isSupportedNetworkId(
|
||||
blockchainId = network.networkId,
|
||||
coinId = tokenMarket.id.value,
|
||||
contractAddress = network.contractAddress,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
hotExcludedBlockchains = hotWalletExcludedBlockchains,
|
||||
hasOnlyHotWallets = hasOnlyHotWallets,
|
||||
)
|
||||
}?.map { network ->
|
||||
TokenMarketInfo.Network(
|
||||
networkId = network.networkId,
|
||||
isExchangeable = false,
|
||||
contractAddress = network.contractAddress,
|
||||
decimalCount = network.decimalCount,
|
||||
)
|
||||
}.orEmpty()
|
||||
val networks = tokenMarket.networks?.filter { network ->
|
||||
BlockchainUtils.isSupportedNetworkId(
|
||||
networkId = network.networkId,
|
||||
coinId = tokenMarket.id.value,
|
||||
contractAddress = network.contractAddress,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
hotExcludedBlockchains = hotWalletExcludedBlockchains,
|
||||
hasOnlyHotWallets = hasOnlyHotWallets,
|
||||
)
|
||||
}?.map { network ->
|
||||
TokenMarketInfo.Network(
|
||||
networkId = network.networkId,
|
||||
isExchangeable = false,
|
||||
contractAddress = network.contractAddress,
|
||||
decimalCount = network.decimalCount,
|
||||
)
|
||||
}.orEmpty()
|
||||
|
||||
addToPortfolioManager = addToPortfolioManagerFactory
|
||||
.create(
|
||||
scope = modelScope,
|
||||
token = param,
|
||||
analyticsParams = AddToPortfolioManager.AnalyticsParams(source = screensSourcesName),
|
||||
).apply {
|
||||
setTokenNetworks(networks)
|
||||
}
|
||||
addToPortfolioManager.setTokenNetworks(networks)
|
||||
addToPortfolioManager.setTokenParams(param)
|
||||
|
||||
addToPortfolioManager?.state
|
||||
?.firstOrNull { it is AddToPortfolioManager.State.AvailableToAdd }
|
||||
?.run { addToPortfolioSlot.activate(AddToPortfolioRoute) }
|
||||
}.saveIn(addToPortfolioJobHolder)
|
||||
addToPortfolioSlot.activate(AddToPortfolioRoute)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
searchQueryState: StateFlow<String>,
|
||||
searchQueryState: StateFlow<SearchQuery>,
|
||||
modelScope: CoroutineScope,
|
||||
screensSourcesName: String,
|
||||
): MarketBlockDelegate
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
package com.tangem.feature.swap.choosetoken.impl.model
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.feature.swap.choosetoken.api.SettingContextUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.feature.swap.choosetoken.api.model.WalletListUM
|
||||
import com.tangem.feature.swap.choosetoken.api.model.WalletTabUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class PortfolioFullBlockDelegate @AssistedInject constructor(
|
||||
private val settingContextUseCase: SettingContextUseCase,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val selectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
@Assisted private val portfolioListBlockDelegate: PortfolioListBlockDelegate,
|
||||
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
|
||||
) {
|
||||
|
||||
private val isSearchingState: Boolean get() = searchQueryState.isSearchingState
|
||||
private val onWalletSelected = Channel<UserWalletId>(capacity = Channel.BUFFERED)
|
||||
|
||||
val selectedWalletFlow: SharedFlow<UserWallet> = onWalletSelected.receiveAsFlow()
|
||||
.distinctUntilChanged()
|
||||
.mapNotNull { walletId -> getUserWalletUseCase.invoke(walletId).getOrNull() }
|
||||
.shareIn(modelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?> = buildFlow()
|
||||
.flowOn(dispatchers.default)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
init {
|
||||
val globalSelectedWallet = selectedWalletUseCase.sync().getOrNull()
|
||||
val allWallets = getWalletsUseCase.invokeSync().filter { it.isMultiCurrency }
|
||||
val firstSelectedWallet = when {
|
||||
globalSelectedWallet?.isMultiCurrency == true -> globalSelectedWallet
|
||||
allWallets.isNotEmpty() -> allWallets.first()
|
||||
else -> null
|
||||
}
|
||||
if (firstSelectedWallet != null) selectWalletTab(firstSelectedWallet.walletId)
|
||||
}
|
||||
|
||||
private fun buildFlow() = flow {
|
||||
val fullPortfolioBlockFlow = combine(
|
||||
flow = getWalletsUseCase.invokeAsMap(),
|
||||
flow2 = portfolioListBlockDelegate.portfolioList,
|
||||
flow3 = selectedWalletFlow.map { wallet -> wallet.walletId }.distinctUntilChanged(),
|
||||
flow4 = settingContextUseCase.invoke(),
|
||||
transform = { allWallets, portfolioList, selectedWalletId, settings ->
|
||||
val tokensListData = portfolioList[selectedWalletId] ?: return@combine null
|
||||
val walletsUM = allWallets.entries
|
||||
.map { (walletId, wallet) ->
|
||||
val searchResultCount: TextReference? = portfolioList[walletId]?.totalTokensCount
|
||||
?.toString()
|
||||
?.let(::stringReference)
|
||||
?.takeIf { isSearchingState }
|
||||
WalletTabUM(
|
||||
text = stringReference(wallet.name),
|
||||
onClick = { selectWalletTab(walletId) },
|
||||
isSelected = selectedWalletId == walletId,
|
||||
count = searchResultCount,
|
||||
)
|
||||
}
|
||||
val walletListUM = if (walletsUM.size != 1) {
|
||||
WalletListUM(walletsUM.toPersistentList())
|
||||
} else {
|
||||
WalletListUM(persistentListOf())
|
||||
}
|
||||
ChooseTokenPortfolioFullBlockUM(
|
||||
walletList = walletListUM,
|
||||
isBalanceHidden = settings.isBalanceHidden,
|
||||
isSearching = isSearchingState,
|
||||
tokensListData = tokensListData,
|
||||
)
|
||||
},
|
||||
)
|
||||
emitAll(fullPortfolioBlockFlow)
|
||||
}
|
||||
|
||||
fun selectWalletTab(walletId: UserWalletId) {
|
||||
onWalletSelected.trySend(walletId)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
modelScope: CoroutineScope,
|
||||
portfolioListBlockDelegate: PortfolioListBlockDelegate,
|
||||
searchQueryState: StateFlow<SearchQuery>,
|
||||
): PortfolioFullBlockDelegate
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.feature.swap.choosetoken.impl.model
|
|||
import com.tangem.common.ui.tokens.TokenConverterParams
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.utils.ExpandedAccountsHolder
|
||||
import com.tangem.domain.account.status.utils.ChooseTokenExpandedAccountsHolder
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
|
|
@ -11,9 +11,14 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResult
|
||||
import com.tangem.feature.swap.choosetoken.api.SettingContextUseCase
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.choosetoken.impl.converter.ChooseTokenListItemConverter
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.utils.extensions.mapNotNullValues
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -22,23 +27,43 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class PortfolioListBlockDelegate @AssistedInject constructor(
|
||||
private val expandedAccountsHolder: ExpandedAccountsHolder,
|
||||
private val expandedAccountsHolder: ChooseTokenExpandedAccountsHolder,
|
||||
private val settingContext: SettingContextUseCase,
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
@Assisted private val searchQueryState: StateFlow<String>,
|
||||
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
|
||||
@Assisted private val featureSettings: ChooseTokenBridge.Settings,
|
||||
) : ClickIntents {
|
||||
|
||||
val onTokenItemClick: Channel<Pair<AccountStatus, CryptoCurrencyStatus>> = Channel()
|
||||
private val onTokenItemClick: Channel<Pair<AccountStatus, CryptoCurrencyStatus>> = Channel()
|
||||
|
||||
val portfolioList: Flow<Map<UserWalletId, TokenListUMData>> = flow {
|
||||
val onTokenChosen: Channel<ChooseTokenResult> = Channel()
|
||||
val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean> =
|
||||
MutableStateFlow { _, _ -> true }
|
||||
|
||||
val portfolioList: SharedFlow<Map<UserWalletId, TokenListUMData>> = buildDataFlow()
|
||||
.distinctUntilChanged()
|
||||
.shareIn(modelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
private fun buildDataFlow(): Flow<Map<UserWalletId, TokenListUMData>> = channelFlow {
|
||||
val allAccountsFlow: Flow<LinkedHashMap<UserWalletId, AccountStatusList>> =
|
||||
multiAccountStatusListSupplier.invokeAsMap()
|
||||
|
||||
val allWalletsFlow: Flow<LinkedHashMap<UserWalletId, UserWallet>> =
|
||||
getWalletsUseCase.invokeAsMap()
|
||||
val allWalletsFlow: StateFlow<LinkedHashMap<UserWalletId, UserWallet>> =
|
||||
getWalletsUseCase.invokeAsMap().stateIn(this)
|
||||
|
||||
onTokenItemClick.receiveAsFlow()
|
||||
.onEach { (account, currencyStatus) ->
|
||||
onTokenItemClick(
|
||||
wallet = allWalletsFlow.value[account.accountId.userWalletId] ?: return@onEach,
|
||||
account = account,
|
||||
currencyStatus = currencyStatus,
|
||||
)
|
||||
}
|
||||
.launchIn(this)
|
||||
|
||||
val expandedAccountsMapFlow = allWalletsFlow
|
||||
.map { allWallets -> allWallets.values.map { wallet -> wallet.walletId } }
|
||||
|
|
@ -51,7 +76,8 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
|
|||
flow2 = allAccountsFlow,
|
||||
flow3 = expandedAccountsMapFlow,
|
||||
flow4 = searchQueryState,
|
||||
transform = { settings, allAccounts, expandedAccountsMap, searchQuery ->
|
||||
flow5 = tokenFilter,
|
||||
transform = { settings, allAccounts, expandedAccountsMap, searchQuery, tokenFilter ->
|
||||
allAccounts.mapNotNullValues { (walletId, statusList) ->
|
||||
val expandedAccounts = expandedAccountsMap[walletId].orEmpty()
|
||||
val converterParams = if (settings.isAccountsMode) {
|
||||
|
|
@ -65,16 +91,16 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
|
|||
params = converterParams,
|
||||
clickIntents = this@PortfolioListBlockDelegate,
|
||||
searchQuery = searchQuery,
|
||||
tokenFilter = tokenFilter,
|
||||
isShowPaymentAccount = featureSettings.isShowPaymentAccount,
|
||||
).convert()
|
||||
|
||||
um
|
||||
}
|
||||
},
|
||||
)
|
||||
emitAll(finalFlow)
|
||||
finalFlow.collectLatest { result -> channel.send(result) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.shareIn(modelScope, SharingStarted.Eagerly, replay = 1)
|
||||
|
||||
private fun List<UserWalletId>.toExpandedAccountsMap(): Flow<Map<UserWalletId, Set<AccountId>>> {
|
||||
if (isEmpty()) return flowOf(emptyMap())
|
||||
|
|
@ -83,6 +109,19 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
|
|||
return combine(flows, { pairs -> pairs.toMap() })
|
||||
}
|
||||
|
||||
private fun onTokenItemClick(wallet: UserWallet, account: AccountStatus, currencyStatus: CryptoCurrencyStatus) {
|
||||
val analyticsPayload = setOf(
|
||||
ChooseTokenAnalyticsPayload.IsSearched(searchQueryState.isSearchingState),
|
||||
)
|
||||
val result = ChooseTokenResult(
|
||||
account = account,
|
||||
currency = currencyStatus,
|
||||
wallet = wallet,
|
||||
analyticsPayload = analyticsPayload,
|
||||
)
|
||||
onTokenChosen.trySend(result)
|
||||
}
|
||||
|
||||
override fun onTokenItemClick(account: AccountStatus, currencyStatus: CryptoCurrencyStatus) {
|
||||
onTokenItemClick.trySend(account to currencyStatus)
|
||||
}
|
||||
|
|
@ -97,7 +136,11 @@ internal class PortfolioListBlockDelegate @AssistedInject constructor(
|
|||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(searchQueryState: StateFlow<String>, modelScope: CoroutineScope): PortfolioListBlockDelegate
|
||||
fun create(
|
||||
searchQueryState: StateFlow<SearchQuery>,
|
||||
modelScope: CoroutineScope,
|
||||
featureSettings: ChooseTokenBridge.Settings,
|
||||
): PortfolioListBlockDelegate
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
package com.tangem.feature.swap.choosetoken.impl.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -17,10 +25,6 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.common.ui.tokens.portfolioTokensList
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
|
||||
|
|
@ -34,15 +38,16 @@ import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
|
|||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.ds.button.TangemButtonType
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.choosetoken.api.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.choosetoken.api.model.WalletListUM
|
||||
import com.tangem.feature.swap.choosetoken.api.model.WalletTabUM
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.ui.market.swapMarketsListItems
|
||||
|
|
@ -54,6 +59,26 @@ import kotlin.random.Random
|
|||
|
||||
private const val LOAD_MORE_BUFFER = 25
|
||||
|
||||
private val ChooseTokenFullUM.isNotFoundState: Boolean
|
||||
get() {
|
||||
if (portfolioBlock == null) return false
|
||||
if (marketsBlock == null) return false
|
||||
return portfolioBlock.tokensListData.tokensList.isEmpty() &&
|
||||
portfolioBlock.isSearching &&
|
||||
marketsBlock !is SwapMarketState.Content &&
|
||||
marketsBlock !is SwapMarketState.Loading
|
||||
}
|
||||
|
||||
private val ChooseTokenFullUM.isEmptyState: Boolean
|
||||
get() {
|
||||
if (portfolioBlock == null) return false
|
||||
if (marketsBlock == null) return false
|
||||
return portfolioBlock.tokensListData.tokensList.isEmpty() &&
|
||||
!portfolioBlock.isSearching &&
|
||||
marketsBlock !is SwapMarketState.Content &&
|
||||
marketsBlock !is SwapMarketState.Loading
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ChooseTokenScreen(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
|
|
@ -93,7 +118,6 @@ private fun Content(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
|
|||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(nestedScrollConnection),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
state = lazyListState,
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
@ -107,22 +131,27 @@ private fun Content(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
|
|||
|
||||
assetsTitle()
|
||||
|
||||
if (state.contentUM != null) {
|
||||
walletListItem(state.contentUM.walletList)
|
||||
if (state.portfolioBlock != null) {
|
||||
walletListItem(state.portfolioBlock.walletList)
|
||||
when {
|
||||
state.isNotFoundState -> tokensNotFound()
|
||||
state.isEmptyState -> emptyTokensList()
|
||||
else -> {
|
||||
tokensListItems(
|
||||
tokensListData = state.portfolioBlock.tokensListData,
|
||||
isBalanceHidden = state.portfolioBlock.isBalanceHidden,
|
||||
)
|
||||
|
||||
tokensListItems(
|
||||
tokensListData = state.contentUM.tokensListData,
|
||||
isBalanceHidden = state.contentUM.isBalanceHidden,
|
||||
)
|
||||
|
||||
if (state.contentUM.marketsState != null) {
|
||||
item("markets_title_spacer") { SpacerH(height = 20.dp) }
|
||||
swapMarketsListItems(state.contentUM.marketsState)
|
||||
if (state.marketsBlock != null) {
|
||||
item("markets_title_spacer") { SpacerH(height = 20.dp) }
|
||||
swapMarketsListItems(state.marketsBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.contentUM?.marketsState != null) {
|
||||
SetupMarketScrollTracker(state.contentUM.marketsState, lazyListState)
|
||||
if (state.marketsBlock != null && !state.isNotFoundState && !state.isEmptyState) {
|
||||
SetupMarketScrollTracker(state.marketsBlock, lazyListState)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,26 +212,59 @@ private fun LazyListScope.assetsTitle() {
|
|||
private fun LazyListScope.walletListItem(walletList: WalletListUM) {
|
||||
if (walletList.items.isEmpty()) return
|
||||
item("wallet_list") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(top = 12.dp, bottom = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
walletList.items.forEach { um ->
|
||||
val colors = when (um.type) {
|
||||
TangemButtonType.Primary -> TangemButtonsDefaults.primaryButtonColors
|
||||
else -> TangemButtonsDefaults.secondaryButtonColors
|
||||
}
|
||||
TangemButton(
|
||||
text = um.text?.resolveReference().orEmpty(),
|
||||
icon = TangemButtonIconPosition.None,
|
||||
size = TangemButtonSize.Action,
|
||||
colors = colors,
|
||||
showProgress = false,
|
||||
onClick = um.onClick,
|
||||
enabled = true,
|
||||
items(walletList.items) { um ->
|
||||
WalletTabItem(um)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WalletTabItem(state: WalletTabUM, modifier: Modifier = Modifier) {
|
||||
val isSelected = state.isSelected
|
||||
val backgroundColor = if (isSelected) TangemTheme.colors.button.primary else TangemTheme.colors.button.secondary
|
||||
val buttonTextColor = if (isSelected) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1
|
||||
val countTextColor = if (isSelected) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.secondary
|
||||
val countBackground = if (isSelected) {
|
||||
TangemTheme.colors.button.secondary.copy(alpha = 0.2f)
|
||||
} else {
|
||||
TangemTheme.colors.button.primary.copy(alpha = 0.1f)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(backgroundColor)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = state.text.resolveReference(),
|
||||
color = buttonTextColor,
|
||||
style = TangemTheme.typography.button,
|
||||
)
|
||||
|
||||
if (state.count != null) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(countBackground, shape = CircleShape)
|
||||
.defaultMinSize(minWidth = 20.dp)
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = state.count.resolveReference(),
|
||||
color = countTextColor,
|
||||
style = TangemTheme.typography.caption1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -253,6 +315,58 @@ private fun LazyListScope.tokensList(items: ImmutableList<TokensListItemUM>, isB
|
|||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.emptyTokensList(modifier: Modifier = Modifier) {
|
||||
item("EmptyTokensList") {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.fillParentMaxSize(),
|
||||
) {
|
||||
Column(modifier = Modifier.align(Alignment.Center)) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size64)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
painter = painterResource(id = R.drawable.ic_no_token_44),
|
||||
colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing16)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing30)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
text = stringResourceSafe(id = R.string.exchange_tokens_empty_tokens),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.tokensNotFound(modifier: Modifier = Modifier) {
|
||||
item("TokensNotFound") {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.fillParentMaxSize(),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing32)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing30)
|
||||
.align(Alignment.TopCenter),
|
||||
text = stringResourceSafe(id = R.string.express_token_list_empty_search),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TokenScreenPreview(@PreviewParameter(ChooseTokenScreenPreviewProvider::class) state: ChooseTokenFullUM) {
|
||||
|
|
@ -321,32 +435,43 @@ private val accounts
|
|||
|
||||
private val wallets
|
||||
get() = persistentListOf(
|
||||
TangemButtonUM(
|
||||
WalletTabUM(
|
||||
text = TextReference.Str(value = "Wallet 1"),
|
||||
type = TangemButtonType.Primary,
|
||||
isSelected = true,
|
||||
onClick = {},
|
||||
count = null,
|
||||
),
|
||||
TangemButtonUM(
|
||||
WalletTabUM(
|
||||
text = TextReference.Str(value = "Wallet 1"),
|
||||
isSelected = true,
|
||||
onClick = {},
|
||||
count = stringReference("3"),
|
||||
),
|
||||
WalletTabUM(
|
||||
text = TextReference.Str(value = "Wallet 2"),
|
||||
type = TangemButtonType.Secondary,
|
||||
isSelected = false,
|
||||
onClick = {},
|
||||
count = stringReference("333"),
|
||||
),
|
||||
TangemButtonUM(
|
||||
WalletTabUM(
|
||||
text = TextReference.Str(value = "Wallet 3"),
|
||||
type = TangemButtonType.Secondary,
|
||||
isSelected = false,
|
||||
onClick = {},
|
||||
count = null,
|
||||
),
|
||||
)
|
||||
|
||||
private val initialUM = ChooseTokenInitialUM(
|
||||
screenTitle = stringReference("Choose token"),
|
||||
onCloseClick = {},
|
||||
searchBar = searchBar,
|
||||
)
|
||||
|
||||
private class ChooseTokenScreenPreviewProvider : PreviewParameterProvider<ChooseTokenFullUM> {
|
||||
override val values: Sequence<ChooseTokenFullUM> = sequenceOf(
|
||||
ChooseTokenFullUM(
|
||||
initialUM = ChooseTokenInitialUM(
|
||||
screenTitle = stringReference("Choose token"),
|
||||
onCloseClick = {},
|
||||
searchBar = searchBar,
|
||||
),
|
||||
contentUM = ChooseTokenUM(
|
||||
initialUM = initialUM,
|
||||
portfolioBlock = ChooseTokenPortfolioFullBlockUM(
|
||||
walletList = WalletListUM(wallets),
|
||||
isBalanceHidden = false,
|
||||
isSearching = false,
|
||||
|
|
@ -354,8 +479,18 @@ private class ChooseTokenScreenPreviewProvider : PreviewParameterProvider<Choose
|
|||
tokensList = accounts,
|
||||
accounts.size,
|
||||
),
|
||||
marketsState = SwapSelectTokenPreviewProvider.defaultState.marketsState,
|
||||
),
|
||||
marketsBlock = SwapSelectTokenPreviewProvider.defaultState.marketsState,
|
||||
),
|
||||
ChooseTokenFullUM(
|
||||
initialUM = initialUM,
|
||||
portfolioBlock = ChooseTokenPortfolioFullBlockUM(
|
||||
walletList = WalletListUM(wallets),
|
||||
isBalanceHidden = false,
|
||||
isSearching = false,
|
||||
tokensListData = TokenListUMData.EmptyList,
|
||||
),
|
||||
marketsBlock = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,31 +1,18 @@
|
|||
package com.tangem.feature.swap.choosetoken.impl.ui
|
||||
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.ds.button.TangemButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.choosetoken.api.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class ChooseTokenFullUM(
|
||||
val initialUM: ChooseTokenInitialUM,
|
||||
val contentUM: ChooseTokenUM?,
|
||||
)
|
||||
|
||||
internal data class ChooseTokenUM(
|
||||
val walletList: WalletListUM,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isSearching: Boolean,
|
||||
val tokensListData: TokenListUMData,
|
||||
val marketsState: SwapMarketState?,
|
||||
val portfolioBlock: ChooseTokenPortfolioFullBlockUM?,
|
||||
val marketsBlock: SwapMarketState?,
|
||||
)
|
||||
|
||||
internal data class ChooseTokenInitialUM(
|
||||
val screenTitle: TextReference,
|
||||
val onCloseClick: () -> Unit,
|
||||
val searchBar: SearchBarUM,
|
||||
)
|
||||
|
||||
internal data class WalletListUM(
|
||||
val items: ImmutableList<TangemButtonUM>,
|
||||
)
|
||||
|
|
@ -2,15 +2,21 @@ package com.tangem.feature.swap.converters
|
|||
|
||||
import com.tangem.common.getTotalCryptoAmount
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.common.ui.account.TokensListPortfolioItemConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.isFlickering
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -29,34 +35,68 @@ internal class AccountTokenItemConverter(
|
|||
private val appCurrency: AppCurrency,
|
||||
private val unavailableErrorText: TextReference,
|
||||
private val expandedAccounts: Map<AccountId, Boolean>,
|
||||
private val onTokenItemClick: (String) -> Unit,
|
||||
private val onAccountItemClick: (Account.CryptoPortfolio) -> Unit,
|
||||
private val onTokenItemClick: (Account, CryptoCurrencyStatus) -> Unit,
|
||||
private val onAccountItemClick: (Account) -> Unit,
|
||||
) : Converter<AccountSwapAvailability, TokensListItemUM.Portfolio> {
|
||||
|
||||
override fun convert(value: AccountSwapAvailability): TokensListItemUM.Portfolio {
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = AccountCryptoPortfolioItemStateConverter(
|
||||
val headerTokenItemState = when (val account = value.account) {
|
||||
is Account.CryptoPortfolio -> AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = value.account.copy(
|
||||
cryptoCurrencies = value.currencyList.map { it.cryptoCurrencyStatus.currency },
|
||||
),
|
||||
account = account.copy(cryptoCurrencies = value.currencyList.map { it.cryptoCurrencyStatus.currency }),
|
||||
onItemClick = onAccountItemClick,
|
||||
).convert(
|
||||
TotalFiatBalance.Loaded(
|
||||
amount = value.currencyList.sumOf { it.cryptoCurrencyStatus.value.fiatAmount.orZero() },
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
),
|
||||
),
|
||||
)
|
||||
is Account.Payment -> createPaymentAccountHeaderState(value)
|
||||
}
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = headerTokenItemState,
|
||||
isExpanded = expandedAccounts[value.account.accountId] != false,
|
||||
isCollapsable = true,
|
||||
tokens = value.currencyList.map { accountSwapCurrency ->
|
||||
createAvailableItemConverter()
|
||||
createAvailableItemConverter(value.account)
|
||||
.convert(accountSwapCurrency.cryptoCurrencyStatus)
|
||||
}.map(TokensListItemUM::Token).toPersistentList(),
|
||||
).convert(Unit)
|
||||
}
|
||||
|
||||
fun createAvailableItemConverter(): TokenItemStateConverter {
|
||||
private fun createPaymentAccountHeaderState(accountSwapAvailability: AccountSwapAvailability): TokenItemState {
|
||||
val account = accountSwapAvailability.account
|
||||
val tokensCount = accountSwapAvailability.currencyList.size
|
||||
val fiatBalance =
|
||||
accountSwapAvailability.currencyList.sumOf { it.cryptoCurrencyStatus.value.fiatAmount.orZero() }
|
||||
return TokenItemState.Content(
|
||||
id = account.accountId.value,
|
||||
iconState = CurrencyIconState.PaymentAccount(),
|
||||
titleState = TokenItemState.TitleState.Content(text = account.accountName.toUM().value),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = pluralReference(
|
||||
R.plurals.common_tokens_count,
|
||||
count = tokensCount,
|
||||
formatArgs = wrappedList(tokensCount),
|
||||
),
|
||||
isAvailable = false,
|
||||
),
|
||||
onItemClick = { onAccountItemClick(account) },
|
||||
fiatAmountState = FiatAmountState.Content(
|
||||
text = fiatBalance.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
isFlickering = false,
|
||||
),
|
||||
subtitle2State = null,
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun createAvailableItemConverter(account: Account): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
subtitleStateProvider = { status ->
|
||||
|
|
@ -70,7 +110,7 @@ internal class AccountTokenItemConverter(
|
|||
fiatAmountStateProvider = {
|
||||
createFiatAmountStateProvider(status = it, appCurrency = appCurrency, isAvailable = true)
|
||||
},
|
||||
onItemClick = { account, currencyStatus -> onTokenItemClick(currencyStatus.currency.id.value) },
|
||||
onItemClick = { _, currencyStatus -> onTokenItemClick(account, currencyStatus) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -16,10 +17,10 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokensDataConverter(
|
||||
private val onSearchEntered: (String) -> Unit,
|
||||
onTokenClick: (String) -> Unit,
|
||||
onAccountClick: (Account.CryptoPortfolio) -> Unit,
|
||||
onTokenClick: (Account, CryptoCurrencyStatus) -> Unit,
|
||||
onAccountClick: (Account) -> Unit,
|
||||
private val expandedAccounts: Map<AccountId, Boolean>,
|
||||
private val onSearchEntered: (String) -> Unit,
|
||||
private val tokensDataState: CurrenciesGroup,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val isAccountsMode: Boolean,
|
||||
|
|
@ -52,7 +53,7 @@ internal class TokensDataConverter(
|
|||
} else {
|
||||
val tokensList = accountList.flatMap { (_, currencyList) ->
|
||||
currencyList.asSequence().map { accountSwapCurrency ->
|
||||
accountListItemConverter.createAvailableItemConverter()
|
||||
accountListItemConverter.createAvailableItemConverter(accountSwapCurrency.account)
|
||||
.convert(accountSwapCurrency.cryptoCurrencyStatus)
|
||||
}.map(TokensListItemUM::Token).toPersistentList()
|
||||
}.toPersistentList()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.feature.swap.DefaultSwapComponent
|
||||
import com.tangem.feature.swap.DefaultSwapFeatureToggles
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
|
|
@ -18,8 +17,8 @@ internal object SwapFeatureModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapFeatureToggles(featureTogglesManager: FeatureTogglesManager): SwapFeatureToggles {
|
||||
return DefaultSwapFeatureToggles(featureTogglesManager)
|
||||
fun provideSwapFeatureToggles(): SwapFeatureToggles {
|
||||
return DefaultSwapFeatureToggles()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
|
|
@ -33,7 +32,6 @@ import com.tangem.core.ui.message.EventMessageAction
|
|||
import com.tangem.core.ui.utils.InputNumberFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
|
|
@ -49,12 +47,14 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
|||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.derivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isHotWallet
|
||||
import com.tangem.domain.pay.WithdrawalResult
|
||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.promo.ShouldShowStoriesUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
|
|
@ -71,6 +71,7 @@ import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNet
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge
|
||||
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
|
||||
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
|
||||
|
|
@ -144,8 +145,8 @@ internal class SwapModel @Inject constructor(
|
|||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val getTangemPayCustomerIdUseCase: GetTangemPayCustomerIdUseCase,
|
||||
private val appsFlyerStore: AppsFlyerStore,
|
||||
private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val paymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase,
|
||||
private val allowPermissionsHandler: AllowPermissionsHandler,
|
||||
chooseTokenBridgeFactory: ChooseTokenBridge.Factory,
|
||||
giveApprovalFeatureToggles: GiveApprovalFeatureToggles,
|
||||
|
|
@ -166,8 +167,7 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
private val swapInteractor = swapInteractorFactory.create(userWalletId)
|
||||
|
||||
val isHoldToConfirmEnabled: Boolean =
|
||||
holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && userWallet.isHotWallet
|
||||
val isHoldToConfirmEnabled: Boolean = userWallet.isHotWallet
|
||||
|
||||
private lateinit var initialFromStatus: CryptoCurrencyStatus
|
||||
private var initialToStatus: CryptoCurrencyStatus? = null
|
||||
|
|
@ -177,7 +177,13 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
val chooseTokenBridge: ChooseTokenBridge = chooseTokenBridgeFactory.create(modelScope)
|
||||
val chooseTokenBridge: ChooseTokenBridge = chooseTokenBridgeFactory.create(
|
||||
modelScope = modelScope,
|
||||
settings = ChooseTokenBridge.Settings.SwapTo,
|
||||
analyticsPayload = setOf(
|
||||
ChooseTokenAnalyticsPayload.ScreensSources(ScreensSources.Swap.value),
|
||||
),
|
||||
)
|
||||
|
||||
private val stateBuilder = StateBuilder(
|
||||
userWalletProvider = Provider { userWallet },
|
||||
|
|
@ -186,7 +192,6 @@ internal class SwapModel @Inject constructor(
|
|||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
isAccountsModeProvider = Provider { isAccountsMode },
|
||||
iGaslessFeeSupportedForNetwork = iGaslessFeeSupportedForNetwork,
|
||||
holdToConfirmButtonFeatureToggles = holdToConfirmButtonFeatureToggles,
|
||||
)
|
||||
|
||||
private val inputNumberFormatter =
|
||||
|
|
@ -222,8 +227,10 @@ internal class SwapModel @Inject constructor(
|
|||
private val swapRouter: SwapRouter = SwapRouter(router = router)
|
||||
private var userCountry: UserCountry? = null
|
||||
|
||||
private var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
private var fromAccount: Account? = null
|
||||
private var toAccount: Account? = null
|
||||
private var fromAccountStatus: CryptoCurrencyStatus? = null
|
||||
private var toAccountStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
/**
|
||||
* If user came from Tangem Pay -> fromAccountCurrencyStatus == null
|
||||
|
|
@ -283,7 +290,7 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
init {
|
||||
chooseTokenBridge.searchQueryState
|
||||
.onEach { query -> onSearchEntered(query) }
|
||||
.onEach { query -> onSearchEntered(query.value) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
chooseTokenBridge.onNewTokenAdded.receiveAsFlow()
|
||||
|
|
@ -293,8 +300,12 @@ internal class SwapModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
|
||||
chooseTokenBridge.onTokenSelected.receiveAsFlow()
|
||||
.onEach { (addedToken, isSearched) ->
|
||||
onTokenSelect(addedToken, isSearched.value)
|
||||
.onEach { result ->
|
||||
onTokenSelect(
|
||||
account = result.account,
|
||||
cryptoCurrencyStatus = result.cryptoCurrencyStatus,
|
||||
isSearched = result.isSearched,
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
||||
|
|
@ -329,22 +340,31 @@ internal class SwapModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
currency = initialCurrencyFrom,
|
||||
).getOrNull()
|
||||
val fromPaymentAccountStatus =
|
||||
paymentAccountCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom).getOrNull()
|
||||
val toAccountStatus = initialCurrencyTo?.let { currencyTo ->
|
||||
getAccountCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = currencyTo,
|
||||
).getOrNull()
|
||||
}
|
||||
val toPaymentAccountStatus = initialCurrencyTo?.let { currencyTo ->
|
||||
paymentAccountCryptoCurrencyStatusUseCase(userWalletId, currencyTo).getOrNull()
|
||||
}
|
||||
val fromAccount = fromAccountStatus?.account ?: fromPaymentAccountStatus?.first
|
||||
val fromStatus = fromAccountStatus?.status ?: fromPaymentAccountStatus?.second
|
||||
|
||||
if (fromAccountStatus == null) {
|
||||
if (fromAccount != null && fromStatus != null) {
|
||||
this@SwapModel.fromAccount = fromAccount
|
||||
this@SwapModel.fromAccountStatus = fromStatus
|
||||
this@SwapModel.toAccount = toAccountStatus?.account ?: toPaymentAccountStatus?.first
|
||||
this@SwapModel.toAccountStatus = toAccountStatus?.status ?: toPaymentAccountStatus?.second
|
||||
this@SwapModel.initialFromStatus = fromStatus
|
||||
this@SwapModel.initialToStatus = toAccountStatus?.status ?: toPaymentAccountStatus?.second
|
||||
initTokens(isInitiallyReversed)
|
||||
} else {
|
||||
showAlert()
|
||||
swapRouter.back()
|
||||
} else {
|
||||
fromAccountCurrencyStatus = fromAccountStatus
|
||||
toAccountCurrencyStatus = toAccountStatus
|
||||
initialFromStatus = fromAccountStatus.status
|
||||
initialToStatus = toAccountStatus?.status
|
||||
initTokens(isInitiallyReversed)
|
||||
}
|
||||
} else {
|
||||
val fromStatus = getFromStatus()
|
||||
|
|
@ -412,7 +432,7 @@ internal class SwapModel @Inject constructor(
|
|||
updateTokensState(state)
|
||||
|
||||
val (selectedCurrency, selectedAccount) = run {
|
||||
var selectedAccountCurrency = toAccountCurrencyStatus
|
||||
var selectedAccountCurrency = toAccountStatus
|
||||
|
||||
if (selectedAccountCurrency == null) {
|
||||
val amountSwapCurrency = swapInteractor.getInitialCurrencyToSwap(
|
||||
|
|
@ -422,14 +442,11 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
|
||||
if (amountSwapCurrency != null) {
|
||||
selectedAccountCurrency = AccountCryptoCurrencyStatus(
|
||||
account = amountSwapCurrency.account,
|
||||
status = amountSwapCurrency.cryptoCurrencyStatus,
|
||||
)
|
||||
selectedAccountCurrency = amountSwapCurrency.cryptoCurrencyStatus
|
||||
}
|
||||
}
|
||||
|
||||
selectedAccountCurrency?.status to selectedAccountCurrency?.account
|
||||
selectedAccountCurrency to toAccount
|
||||
}
|
||||
|
||||
val isApplied = applyInitialTokenChoice(
|
||||
|
|
@ -541,7 +558,7 @@ internal class SwapModel @Inject constructor(
|
|||
private fun applyInitialTokenChoice(
|
||||
state: TokensDataStateExpress,
|
||||
selectedCurrency: CryptoCurrencyStatus?,
|
||||
selectedAccount: Account.CryptoPortfolio?,
|
||||
selectedAccount: Account?,
|
||||
isReverseFromTo: Boolean,
|
||||
): Boolean {
|
||||
// exceptional case
|
||||
|
|
@ -562,9 +579,9 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
val (fromAccount, toAccount) = if (canUseFromAccountCurrencyStatus) {
|
||||
if (isOrderReversed.value) {
|
||||
selectedAccount to requireNotNull(fromAccountCurrencyStatus).account
|
||||
selectedAccount to fromAccount
|
||||
} else {
|
||||
requireNotNull(fromAccountCurrencyStatus).account to selectedAccount
|
||||
fromAccount to selectedAccount
|
||||
}
|
||||
} else {
|
||||
null to null
|
||||
|
|
@ -607,9 +624,9 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun startLoadingQuotes(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
|
|
@ -680,9 +697,9 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun loadQuotesTask(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
|
|
@ -784,7 +801,7 @@ internal class SwapModel @Inject constructor(
|
|||
selectedFeeType = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
hideFee = tangemPayInput?.isWithdrawal == true,
|
||||
hideFee = isTangemPayWithdrawal(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -873,11 +890,11 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun sendErrorAnalyticsEvent(error: ExpressDataError, provider: SwapProvider) {
|
||||
val receiveToken = dataState.toCryptoCurrency?.currency?.let { currency ->
|
||||
"${currency.network.backendId}:${currency.symbol}"
|
||||
"${currency.network.rawId}:${currency.symbol}"
|
||||
}
|
||||
analyticsErrorEventHandler.sendErrorEvent(
|
||||
SwapEvents.NoticeProviderError(
|
||||
sendToken = "${initialCurrencyFrom.network.backendId}:${initialCurrencyFrom.symbol}",
|
||||
sendToken = "${initialCurrencyFrom.network.rawId}:${initialCurrencyFrom.symbol}",
|
||||
receiveToken = receiveToken.orEmpty(),
|
||||
provider = provider,
|
||||
errorCode = error.code,
|
||||
|
|
@ -978,8 +995,9 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
val fromCurrency = requireNotNull(dataState.fromCryptoCurrency)
|
||||
val fee = getSelectedFee()
|
||||
val isTangemPayWithdrawal = isTangemPayWithdrawal()
|
||||
|
||||
if (fee == null && tangemPayInput?.isWithdrawal != true) {
|
||||
if (fee == null && !isTangemPayWithdrawal) {
|
||||
TangemLogger.e("onSwapClick: fee is null and isWithdrawal is ${tangemPayInput?.isWithdrawal}")
|
||||
showAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
|
||||
modelScope.launch {
|
||||
|
|
@ -1001,7 +1019,7 @@ internal class SwapModel @Inject constructor(
|
|||
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
|
||||
fee = fee,
|
||||
expressOperationType = ExpressOperationType.SWAP,
|
||||
isTangemPayWithdrawal = tangemPayInput?.isWithdrawal == true,
|
||||
isTangemPayWithdrawal = isTangemPayWithdrawal,
|
||||
)
|
||||
}.onSuccess { swapTransactionState ->
|
||||
when (swapTransactionState) {
|
||||
|
|
@ -1166,7 +1184,7 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
runCatching(dispatchers.io) {
|
||||
swapInteractor.givePermissionToSwap(
|
||||
networkId = fromToken.network.backendId,
|
||||
networkId = fromToken.network.rawId,
|
||||
permissionOptions = PermissionOptions(
|
||||
approveData = approveDataModel,
|
||||
forTokenContractAddress = (fromToken as? CryptoCurrency.Token)?.contractAddress.orEmpty(),
|
||||
|
|
@ -1259,11 +1277,12 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun onTokenSelect(id: String, isSearched: Boolean) {
|
||||
private fun onTokenSelect(account: Account, cryptoCurrencyStatus: CryptoCurrencyStatus, isSearched: Boolean) {
|
||||
val tokens = dataState.tokensDataState ?: return
|
||||
val (foundToken, foundAccount) = getSelectedTokenAndAccount(tokens, id)
|
||||
val foundToken = cryptoCurrencyStatus
|
||||
val foundAccount = account
|
||||
|
||||
foundToken?.currency?.symbol?.let { symbol ->
|
||||
foundToken.currency.symbol.let { symbol ->
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ChooseTokenScreenResult(isTokenChosen = true, token = symbol),
|
||||
)
|
||||
|
|
@ -1277,108 +1296,90 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
if (foundToken != null) {
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val fromAccount: Account.CryptoPortfolio?
|
||||
val toToken: CryptoCurrencyStatus
|
||||
val toAccount: Account.CryptoPortfolio?
|
||||
if (isOrderReversed.value) {
|
||||
fromToken = foundToken
|
||||
fromAccount = foundAccount
|
||||
toToken = initialFromStatus
|
||||
toAccount = fromAccountCurrencyStatus?.account
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val fromAccount: Account?
|
||||
val toToken: CryptoCurrencyStatus
|
||||
val toAccount: Account?
|
||||
if (isOrderReversed.value) {
|
||||
fromToken = foundToken
|
||||
fromAccount = foundAccount
|
||||
toToken = initialFromStatus
|
||||
toAccount = this.fromAccount
|
||||
|
||||
val newToken = fromToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
} else {
|
||||
fromTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
val newToken = fromToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
} else {
|
||||
fromToken = initialFromStatus
|
||||
fromAccount = fromAccountCurrencyStatus?.account
|
||||
toToken = foundToken
|
||||
toAccount = foundAccount
|
||||
|
||||
val newToken = toToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
} else {
|
||||
toTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
fromTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
|
||||
if (dataState.fromCryptoCurrency != null && dataState.tokensDataState != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toCryptoCurrency = toToken,
|
||||
toAccount = toAccount,
|
||||
selectedProvider = null,
|
||||
)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
if (handleSwapNotSupported(
|
||||
state = tokens,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromAccount = fromAccount,
|
||||
toAccount = toAccount,
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
TangemLogger.i(
|
||||
"updateFeePaidCryptoCurrencyFor: id = ${fromToken.currency.id}, " +
|
||||
"isOrderReversed: ${isOrderReversed.value}",
|
||||
)
|
||||
if ((uiState.sendCardData as? SwapCardState.SwapCardData)?.type is TransactionCardType.ReadOnly) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = fromToken.currency,
|
||||
initialCurrencyTo = toToken.currency,
|
||||
fromNetworkInfo = fromToken.currency.getNetworkInfo(),
|
||||
)
|
||||
}
|
||||
updateFeePaidCryptoCurrencyFor(fromToken)
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = toAccount,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
updateTokensState(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedTokenAndAccount(
|
||||
tokens: TokensDataStateExpress,
|
||||
id: String,
|
||||
): Pair<CryptoCurrencyStatus?, Account.CryptoPortfolio?> {
|
||||
val accountCryptoCurrencyStatus = if (isOrderReversed.value) {
|
||||
tokens.fromGroup
|
||||
} else {
|
||||
tokens.toGroup
|
||||
}.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
|
||||
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
|
||||
accountSwapCurrency.cryptoCurrencyStatus.currency.id.value == id
|
||||
fromToken = initialFromStatus
|
||||
fromAccount = this.fromAccount
|
||||
toToken = foundToken
|
||||
toAccount = foundAccount
|
||||
|
||||
val newToken = toToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
} else {
|
||||
toTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
}
|
||||
return accountCryptoCurrencyStatus?.cryptoCurrencyStatus to accountCryptoCurrencyStatus?.account
|
||||
|
||||
if (dataState.fromCryptoCurrency != null && dataState.tokensDataState != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toCryptoCurrency = toToken,
|
||||
toAccount = toAccount,
|
||||
selectedProvider = null,
|
||||
)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
if (handleSwapNotSupported(
|
||||
state = tokens,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromAccount = fromAccount,
|
||||
toAccount = toAccount,
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
TangemLogger.i(
|
||||
"updateFeePaidCryptoCurrencyFor: id = ${fromToken.currency.id}, " +
|
||||
"isOrderReversed: ${isOrderReversed.value}",
|
||||
)
|
||||
if ((uiState.sendCardData as? SwapCardState.SwapCardData)?.type is TransactionCardType.ReadOnly) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = fromToken.currency,
|
||||
initialCurrencyTo = toToken.currency,
|
||||
fromNetworkInfo = fromToken.currency.getNetworkInfo(),
|
||||
)
|
||||
}
|
||||
updateFeePaidCryptoCurrencyFor(fromToken)
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = toAccount,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
updateTokensState(tokens)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
|
@ -1930,8 +1931,8 @@ internal class SwapModel @Inject constructor(
|
|||
state: TokensDataStateExpress,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
): Boolean {
|
||||
val selectedCurrency = if (isOrderReversed.value) fromToken else toToken
|
||||
if (isTokenAvailableForSwap(state, selectedCurrency, isOrderReversed.value)) return false
|
||||
|
|
@ -1979,8 +1980,12 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isTangemPayWithdrawal(): Boolean {
|
||||
return tangemPayInput?.isWithdrawal == true || dataState.fromAccount is Account.Payment
|
||||
}
|
||||
|
||||
private fun List<SwapProvider>.filterForTangemPayWithdrawal(): List<SwapProvider> {
|
||||
return if (tangemPayInput?.isWithdrawal == true) {
|
||||
return if (isTangemPayWithdrawal()) {
|
||||
filter { it.type == ExchangeProviderType.CEX }
|
||||
} else {
|
||||
this
|
||||
|
|
@ -2117,8 +2122,7 @@ internal class SwapModel @Inject constructor(
|
|||
saveBlockchainErrorUseCase(
|
||||
error = BlockchainErrorInfo(
|
||||
errorMessage = errorMessage,
|
||||
blockchainId = network.rawId,
|
||||
derivationPath = network.derivationPath.value,
|
||||
networkId = network.id,
|
||||
destinationAddress = transaction?.txTo.orEmpty(),
|
||||
tokenSymbol = fromCurrencyStatus.currency.symbol,
|
||||
amount = dataState.amount.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ data class SwapProcessDataState(
|
|||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val feePaidCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val fromAccount: Account.CryptoPortfolio? = null,
|
||||
val toAccount: Account.CryptoPortfolio? = null,
|
||||
val fromAccount: Account? = null,
|
||||
val toAccount: Account? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class SwapSelectTokenStateHolder(
|
||||
val marketsState: SwapMarketState,
|
||||
|
|
@ -14,32 +11,6 @@ internal data class SwapSelectTokenStateHolder(
|
|||
val onSearchEntered: (String) -> Unit,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TokenListUMData {
|
||||
|
||||
val tokensList: ImmutableList<TokensListItemUM>
|
||||
val totalTokensCount: Int
|
||||
|
||||
data class AccountList(
|
||||
override val tokensList: ImmutableList<TokensListItemUM.Portfolio>,
|
||||
override val totalTokensCount: Int,
|
||||
) : TokenListUMData
|
||||
|
||||
data class TokenList(
|
||||
override val tokensList: ImmutableList<TokensListItemUM>,
|
||||
override val totalTokensCount: Int,
|
||||
) : TokenListUMData
|
||||
|
||||
data object EmptyList : TokenListUMData {
|
||||
override val tokensList: ImmutableList<TokensListItemUM> = persistentListOf()
|
||||
override val totalTokensCount: Int = EMPTY_TOKENS_COUNT
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EMPTY_TOKENS_COUNT = 0
|
||||
}
|
||||
}
|
||||
|
||||
internal val SwapSelectTokenStateHolder.isNotFoundState: Boolean
|
||||
get() =
|
||||
tokensListData.tokensList.isEmpty() && isAfterSearch &&
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ package com.tangem.feature.swap.ui
|
|||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.account.AccountIconUM
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
|
||||
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.*
|
||||
|
|
@ -58,11 +58,9 @@ internal class StateBuilder(
|
|||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val isAccountsModeProvider: Provider<Boolean>,
|
||||
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
|
||||
holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
|
||||
) {
|
||||
|
||||
private val isHoldToConfirmEnabled: Boolean =
|
||||
holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled && userWalletProvider().isHotWallet
|
||||
private val isHoldToConfirmEnabled: Boolean = userWalletProvider().isHotWallet
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
|
|
@ -89,7 +87,7 @@ internal class StateBuilder(
|
|||
token = null,
|
||||
tokenIconUrl = initialCurrencyFrom.iconUrl,
|
||||
tokenCurrency = initialCurrencyFrom.symbol,
|
||||
coinId = initialCurrencyFrom.network.backendId,
|
||||
coinId = initialCurrencyFrom.network.rawId,
|
||||
canSelectAnotherToken = false,
|
||||
isNotNativeToken = initialCurrencyFrom is CryptoCurrency.Token,
|
||||
balance = "",
|
||||
|
|
@ -107,7 +105,7 @@ internal class StateBuilder(
|
|||
balance = "",
|
||||
isNotNativeToken = initialCurrencyTo is CryptoCurrency.Token,
|
||||
networkIconRes = initialCurrencyTo?.let { getActiveIconRes(it.network.rawId) },
|
||||
coinId = initialCurrencyTo?.network?.backendId,
|
||||
coinId = initialCurrencyTo?.network?.rawId,
|
||||
isBalanceHidden = true,
|
||||
),
|
||||
fee = FeeItemState.Empty,
|
||||
|
|
@ -144,7 +142,7 @@ internal class StateBuilder(
|
|||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = fromToken,
|
||||
tokenIconUrl = fromToken.currency.iconUrl,
|
||||
coinId = fromToken.currency.network.backendId,
|
||||
coinId = fromToken.currency.network.rawId,
|
||||
isNotNativeToken = fromToken.currency is CryptoCurrency.Token,
|
||||
tokenCurrency = fromToken.currency.symbol,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
|
|
@ -177,8 +175,8 @@ internal class StateBuilder(
|
|||
uiStateHolder: SwapStateHolder,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
mainTokenId: String,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.currency.id.value
|
||||
|
|
@ -195,7 +193,7 @@ internal class StateBuilder(
|
|||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = fromToken,
|
||||
tokenIconUrl = fromToken.currency.iconUrl,
|
||||
coinId = fromToken.currency.network.backendId,
|
||||
coinId = fromToken.currency.network.rawId,
|
||||
isNotNativeToken = fromToken.currency is CryptoCurrency.Token,
|
||||
tokenCurrency = fromToken.currency.symbol,
|
||||
canSelectAnotherToken = canSelectSendToken,
|
||||
|
|
@ -213,7 +211,7 @@ internal class StateBuilder(
|
|||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = toToken,
|
||||
tokenIconUrl = toToken.currency.iconUrl,
|
||||
coinId = toToken.currency.network.backendId,
|
||||
coinId = toToken.currency.network.rawId,
|
||||
isNotNativeToken = toToken.currency is CryptoCurrency.Token,
|
||||
tokenCurrency = toToken.currency.symbol,
|
||||
canSelectAnotherToken = canSelectReceiveToken,
|
||||
|
|
@ -241,8 +239,8 @@ internal class StateBuilder(
|
|||
fromToken: CryptoCurrency,
|
||||
toToken: CryptoCurrency,
|
||||
mainTokenId: String,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.id.value
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
|
|
@ -265,7 +263,7 @@ internal class StateBuilder(
|
|||
token = uiStateHolder.sendCardData.token,
|
||||
tokenIconUrl = fromToken.iconUrl,
|
||||
tokenCurrency = fromToken.symbol,
|
||||
coinId = fromToken.network.backendId,
|
||||
coinId = fromToken.network.rawId,
|
||||
isNotNativeToken = fromToken is CryptoCurrency.Token,
|
||||
canSelectAnotherToken = canSelectSendToken,
|
||||
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
|
||||
|
|
@ -281,7 +279,7 @@ internal class StateBuilder(
|
|||
token = uiStateHolder.receiveCardData.token,
|
||||
tokenIconUrl = toToken.iconUrl,
|
||||
tokenCurrency = toToken.symbol,
|
||||
coinId = toToken.network.backendId,
|
||||
coinId = toToken.network.rawId,
|
||||
isNotNativeToken = toToken is CryptoCurrency.Token,
|
||||
canSelectAnotherToken = canSelectReceiveToken,
|
||||
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
|
||||
|
|
@ -373,7 +371,7 @@ internal class StateBuilder(
|
|||
amountEquivalent = getFormattedFiatAmount(quoteModel.fromTokenInfo.amountFiat),
|
||||
token = fromCurrencyStatus,
|
||||
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
|
||||
coinId = fromCurrencyStatus.currency.network.backendId,
|
||||
coinId = fromCurrencyStatus.currency.network.rawId,
|
||||
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
|
|
@ -414,7 +412,7 @@ internal class StateBuilder(
|
|||
},
|
||||
token = toCurrencyStatus,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = toCurrencyStatus.currency.network.backendId,
|
||||
coinId = toCurrencyStatus.currency.network.rawId,
|
||||
isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
|
|
@ -502,7 +500,7 @@ internal class StateBuilder(
|
|||
swapProvider: SwapProvider,
|
||||
fromToken: TokenSwapInfo,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
expressDataError: ExpressDataError,
|
||||
isReverseSwapPossible: Boolean,
|
||||
|
|
@ -535,7 +533,7 @@ internal class StateBuilder(
|
|||
amountEquivalent = getFormattedFiatAmount(BigDecimal.ZERO),
|
||||
token = toToken,
|
||||
tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
|
||||
coinId = toToken.currency.network.backendId,
|
||||
coinId = toToken.currency.network.rawId,
|
||||
isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
|
||||
|
|
@ -619,7 +617,7 @@ internal class StateBuilder(
|
|||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
toTokenStatus: CryptoCurrencyStatus?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -694,7 +692,7 @@ internal class StateBuilder(
|
|||
amountFormatted: String,
|
||||
amountRaw: String,
|
||||
fromToken: CryptoCurrency,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
minTxAmount: BigDecimal?,
|
||||
): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
|
|
@ -1331,30 +1329,37 @@ internal class StateBuilder(
|
|||
return FCA_RESTRICTED_PROVIDER_IDS.contains(providerId)
|
||||
}
|
||||
|
||||
private fun getFromCardAccountTitle(fromAccount: Account.CryptoPortfolio?): AccountTitleUM {
|
||||
private fun getFromCardAccountTitle(fromAccount: Account?): AccountTitleUM {
|
||||
return if (fromAccount != null && isAccountsModeProvider()) {
|
||||
AccountTitleUM.Account(
|
||||
prefixText = resourceReference(R.string.common_from),
|
||||
name = fromAccount.accountName.toUM().value,
|
||||
icon = CryptoPortfolioIconConverter.convert(fromAccount.icon),
|
||||
icon = fromAccount.toIconUM(),
|
||||
)
|
||||
} else {
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_from_title))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToCardAccountTitle(toAccount: Account.CryptoPortfolio?): AccountTitleUM {
|
||||
private fun getToCardAccountTitle(toAccount: Account?): AccountTitleUM {
|
||||
return if (toAccount != null && isAccountsModeProvider()) {
|
||||
AccountTitleUM.Account(
|
||||
prefixText = resourceReference(R.string.common_to),
|
||||
name = toAccount.accountName.toUM().value,
|
||||
icon = CryptoPortfolioIconConverter.convert(toAccount.icon),
|
||||
icon = toAccount.toIconUM(),
|
||||
)
|
||||
} else {
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_to_title))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Account.toIconUM(): AccountIconUM {
|
||||
return when (this) {
|
||||
is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(icon)
|
||||
is Account.Payment -> AccountIconUM.Payment
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChangeCardsButtonState(isReverseSwapPossible: Boolean) = if (isReverseSwapPossible) {
|
||||
ChangeCardsButtonState.ENABLED
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||
import com.tangem.core.ui.test.MainScreenTestTags
|
||||
import com.tangem.core.ui.utils.lazyListItemPosition
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.models.isEmptyState
|
||||
import com.tangem.feature.swap.models.isNotFoundState
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.feature.swap.choosetoken.api.model.TokenListUMData
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
import com.tangem.feature.swap.models.market.state.SwapMarketState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue