diff --git a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt index 1589246e98..9f411a7900 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SwapDomainModule.kt @@ -4,7 +4,6 @@ import com.tangem.domain.swap.SwapErrorResolver import com.tangem.domain.swap.SwapRepositoryV2 import com.tangem.domain.swap.SwapTransactionRepository import com.tangem.domain.swap.usecase.* -import com.tangem.feature.swap.domain.GetAvailablePairsUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -19,12 +18,6 @@ import com.tangem.feature.swap.domain.api.SwapRepository as OldSwapRepository @InstallIn(SingletonComponent::class) internal object SwapDomainModule { - @Provides - @Singleton - fun provideGetAvailablePairsUseCase(swapRepository: OldSwapRepository): GetAvailablePairsUseCase { - return GetAvailablePairsUseCase(swapRepository = swapRepository) - } - @Provides @Singleton fun provideGetSwapSupportedPairsUseCase( diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 8f1f852eb9..22d8642887 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -71,7 +71,6 @@ internal class ChildFactory @Inject constructor( private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory, - private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory, private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory, private val newWelcomeComponentFactory: NewWelcomeComponent.Factory, private val storiesComponentFactory: StoriesComponent.Factory, @@ -253,13 +252,6 @@ internal class ChildFactory @Inject constructor( componentFactory = sellCryptoComponentFactory, ) } - is AppRoute.SwapCrypto -> { - createComponentChild( - context = context, - params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId), - componentFactory = swapSelectTokensComponentFactory, - ) - } is AppRoute.Onboarding -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 1f04371c14..00c067b335 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -331,11 +331,6 @@ sealed class AppRoute(val path: String) : Route { val userWalletId: UserWalletId, ) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}") - @Serializable - data class SwapCrypto( - val userWalletId: UserWalletId, - ) : AppRoute(path = "/swap_crypto/${userWalletId.stringValue}") - /** * Onboarding V2 * @property scanResponse scan response, determines onboarding route by the product type diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt index b09ed9c19d..5440a2e92f 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt @@ -82,8 +82,6 @@ sealed class MainScreenAnalyticsEvent( class BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened") - class SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened") - class SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened") data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent( @@ -96,21 +94,6 @@ sealed class MainScreenAnalyticsEvent( params = mapOf(TOKEN_PARAM to currencySymbol), ) - data class SwapTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent( - event = "Swap Token Clicked", - params = mapOf(TOKEN_PARAM to currencySymbol), - ) - - data class ReceiveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent( - event = "Receive Token Clicked", - params = mapOf(TOKEN_PARAM to currencySymbol), - ) - - data class RemoveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent( - event = "Remove Button Clicked", - params = mapOf(TOKEN_PARAM to currencySymbol), - ) - data class ButtonClose(val source: AnalyticsParam.ScreensSources) : MainScreenAnalyticsEvent( event = "Button - Close", params = mapOf(AnalyticsParam.SOURCE to source.value), diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SwapAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SwapAnalyticsEvent.kt index 74b68afc09..7a4f738c72 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SwapAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/SwapAnalyticsEvent.kt @@ -15,19 +15,6 @@ sealed class SwapAnalyticsEvent( params: Map = emptyMap(), ) : AnalyticsEvent("Swap", event, params) { - data class TokenSelected( - val token: String, - val source: ScreensSources, - val isSearched: Boolean, - ) : SwapAnalyticsEvent( - event = "Token Selected", - params = mapOf( - TOKEN_PARAM to token, - SOURCE to source.value, - SEARCHED to if (isSearched) "True" else "False", - ), - ) - class FilterProvider(filterType: String) : SwapAnalyticsEvent( event = "Filter Provider", params = mapOf(TYPE to filterType), diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt deleted file mode 100644 index 279c7b4747..0000000000 --- a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/SwapSelectTokensComponent.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.features.onramp.component - -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.models.wallet.UserWalletId - -/** - * Swap select tokens component - * -[REDACTED_AUTHOR] - */ -interface SwapSelectTokensComponent : ComposableContentComponent { - - interface Factory : ComponentFactory - - /** - * Params - * - * @property userWalletId user wallet id - */ - data class Params(val userWalletId: UserWalletId) -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt deleted file mode 100644 index d18755aaf4..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/DefaultSwapSelectTokensComponent.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.tangem.features.onramp.swap - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.router.slot.childSlot -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.context.child -import com.tangem.core.decompose.context.childByContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent -import com.tangem.features.onramp.component.SwapSelectTokensComponent -import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent -import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute -import com.tangem.features.onramp.swap.model.SwapSelectTokensModel -import com.tangem.features.onramp.swap.ui.SwapSelectTokens -import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent -import com.tangem.features.onramp.tokenlist.entity.OnrampOperation -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -@Stable -internal class DefaultSwapSelectTokensComponent @AssistedInject constructor( - tokenListComponentFactory: OnrampTokenListComponent.Factory, - availableSwapPairsComponentFactory: AvailableSwapPairsComponent.Factory, - analyticsEventHandler: AnalyticsEventHandler, - private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory, - @Assisted private val appComponentContext: AppComponentContext, - @Assisted private val params: SwapSelectTokensComponent.Params, -) : AppComponentContext by appComponentContext, SwapSelectTokensComponent { - - private val model: SwapSelectTokensModel = getOrCreateModel(params) - - private val selectFromTokenListComponent: OnrampTokenListComponent = tokenListComponentFactory.create( - context = child(key = "select_from_token_list"), - params = OnrampTokenListComponent.Params( - filterOperation = OnrampOperation.SWAP, - userWalletId = params.userWalletId, - onTokenClick = model::selectFromToken, - ), - ) - - private val selectToTokenListComponent: AvailableSwapPairsComponent = availableSwapPairsComponentFactory.create( - context = child(key = "select_to_token_list"), - params = AvailableSwapPairsComponent.Params( - userWalletId = params.userWalletId, - selectedStatus = model.fromCurrencyStatus, - onTokenClick = model::selectToToken, - ), - ) - - private val bottomSheetSlot = childSlot( - source = selectToTokenListComponent.bottomSheetNavigation, - serializer = AddToPortfolioRoute.serializer(), - key = "add_to_portfolio_bottom_sheet", - handleBackButton = false, - childFactory = { _, context -> bottomSheetChild(context) }, - ) - - init { - analyticsEventHandler.send(event = MainScreenAnalyticsEvent.SwapScreenOpened()) - } - - @Suppress("UnsafeCallOnNullableType") - private fun bottomSheetChild(componentContext: ComponentContext): ComposableBottomSheetComponent { - return addToPortfolioComponentFactory.create( - context = childByContext(componentContext), - params = AddToPortfolioComponent.Params( - addToPortfolioManager = selectToTokenListComponent.addToPortfolioManager, - ), - ) - } - - @Composable - override fun Content(modifier: Modifier) { - val state by model.state.collectAsStateWithLifecycle() - val fromTokensState by selectFromTokenListComponent.uiState.collectAsStateWithLifecycle() - val toTokensState by selectToTokenListComponent.uiState.collectAsStateWithLifecycle() - val bottomSheet by bottomSheetSlot.subscribeAsState() - - SwapSelectTokens( - state = state, - selectFromTokenListComponent = selectFromTokenListComponent, - selectFromTokenListState = fromTokensState, - selectToTokenListComponent = selectToTokenListComponent, - selectToTokenListState = toTokensState, - modifier = modifier, - ) - - bottomSheet.child?.instance?.BottomSheet() - } - - @AssistedFactory - interface Factory : SwapSelectTokensComponent.Factory { - - override fun create( - context: AppComponentContext, - params: SwapSelectTokensComponent.Params, - ): DefaultSwapSelectTokensComponent - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt deleted file mode 100644 index 2292f1bdf8..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/AvailableSwapPairsComponent.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs - -import androidx.compose.runtime.Stable -import com.arkivanov.decompose.router.slot.SlotNavigation -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.decompose.ComposableListContentComponent -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager -import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import kotlinx.coroutines.flow.StateFlow - -/** Token list component that present list of available tokens for swap */ -@Stable -internal interface AvailableSwapPairsComponent : ComposableListContentComponent { - - val bottomSheetNavigation: SlotNavigation - val addToPortfolioManager: AddToPortfolioManager - - /** Component factory */ - interface Factory : ComponentFactory - - /** - * Params - * - * @property userWalletId id of multi-currency wallet - * @property selectedStatus flow of selected status - * @property onTokenClick callback for token click - */ - data class Params( - val userWalletId: UserWalletId, - val selectedStatus: StateFlow, - val onTokenClick: (TokenItemState, CryptoCurrencyStatus) -> Unit, - ) -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt deleted file mode 100644 index 347d4ad933..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/DefaultAvailableSwapPairsComponent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs - -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.runtime.Stable -import androidx.compose.ui.Modifier -import com.arkivanov.decompose.router.slot.SlotNavigation -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager -import com.tangem.features.onramp.swap.availablepairs.model.AddToPortfolioRoute -import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.ui.onrampSwapTokenList -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.StateFlow - -@Stable -internal class DefaultAvailableSwapPairsComponent @AssistedInject constructor( - @Assisted context: AppComponentContext, - @Assisted params: AvailableSwapPairsComponent.Params, -) : AvailableSwapPairsComponent, AppComponentContext by context { - - private val model: AvailableSwapPairsModel = getOrCreateModel(params) - - override val bottomSheetNavigation: SlotNavigation get() = model.bottomSheetNavigation - override val addToPortfolioManager: AddToPortfolioManager get() = model.addToPortfolioManager - - override val uiState: StateFlow - get() = model.state - - override fun LazyListScope.content(uiState: TokenListUM, modifier: Modifier) { - onrampSwapTokenList(state = uiState) - } - - @AssistedFactory - interface Factory : AvailableSwapPairsComponent.Factory { - override fun create( - context: AppComponentContext, - params: AvailableSwapPairsComponent.Params, - ): DefaultAvailableSwapPairsComponent - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt deleted file mode 100644 index 0f451e0008..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsComponentModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.di - -import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent -import com.tangem.features.onramp.swap.availablepairs.DefaultAvailableSwapPairsComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface AvailableSwapPairsComponentModule { - - @Binds - @Singleton - fun bindAvailableSwapPairsComponentFactory( - factory: DefaultAvailableSwapPairsComponent.Factory, - ): AvailableSwapPairsComponent.Factory -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt deleted file mode 100644 index b68b705703..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/di/AvailableSwapPairsModelModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.di - -import com.tangem.core.decompose.di.ModelComponent -import com.tangem.core.decompose.model.Model -import com.tangem.features.onramp.swap.availablepairs.model.AvailableSwapPairsModel -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(ModelComponent::class) -internal interface AvailableSwapPairsModelModule { - - @Binds - @IntoMap - @ClassKey(AvailableSwapPairsModel::class) - fun bindAvailableSwapPairsModel(model: AvailableSwapPairsModel): Model -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetErrorWarningTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetErrorWarningTransformer.kt deleted file mode 100644 index d3410bd2dc..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetErrorWarningTransformer.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.entity.transformers - -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.feature.swap.domain.models.ExpressException -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import kotlinx.collections.immutable.persistentListOf - -/** -[REDACTED_AUTHOR] - */ -internal class SetErrorWarningTransformer( - private val cause: Throwable, - private val onRefresh: () -> Unit, -) : TokenListUMTransformer { - - override fun transform(prevState: TokenListUM): TokenListUM { - return prevState.copy( - availableItems = persistentListOf(), - unavailableItems = persistentListOf(), - warning = NotificationUM.Warning.OnrampErrorNotification( - errorCode = (cause as? ExpressException)?.expressDataError?.code?.toString(), - onRefresh = onRefresh, - ), - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt deleted file mode 100644 index 02b9d632dc..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetLoadingTokenItemsTransformer.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.entity.transformers - -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -/** - * Set [statuses] as loading items - * -[REDACTED_AUTHOR] - */ -internal class SetLoadingTokenItemsTransformer( - private val statuses: List, -) : TokenListUMTransformer { - - override fun transform(prevState: TokenListUM): TokenListUM { - return prevState.copy( - availableItems = LoadingTokenListItemConverter.convertList( - input = statuses.map(CryptoCurrencyStatus::currency), - ).toImmutableList(), - unavailableItems = persistentListOf(), - warning = null, - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformer.kt deleted file mode 100644 index 08f93f0bd0..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/transformers/SetNoAvailablePairsTransformer.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.entity.transformers - -import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter -import com.tangem.common.ui.account.TokensListPortfolioItemConverter -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM -import com.tangem.core.ui.extensions.TextReference -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.currency.CryptoCurrencyStatus -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMData -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList - -internal class SetNoAvailablePairsTransformer( - private val appCurrency: AppCurrency, - private val accountList: Map>, - private val isBalanceHidden: Boolean, - private val isAccountsMode: Boolean, - private val unavailableErrorText: TextReference, -) : TokenListUMTransformer { - private val unavailableConverter = OnrampTokenItemStateConverterFactory - .createUnavailableItemConverterV2(appCurrency = appCurrency, unavailableErrorText = unavailableErrorText) - - override fun transform(prevState: TokenListUM): TokenListUM { - val totalTokensCount = accountList.values.sumOf { it.size } - - return prevState.copy( - availableItems = persistentListOf(), - unavailableItems = persistentListOf(), - tokensListData = if (isAccountsMode) { - TokenListUMData.AccountList( - tokensList = accountList.map { (account, cryptoCurrencies) -> - TokensListPortfolioItemConverter( - tokenItemUM = AccountCryptoPortfolioItemStateConverter( - appCurrency = appCurrency, - account = account, - onItemClick = null, - ).convert(TotalFiatBalance.Failed), - isExpanded = true, - isCollapsable = false, - tokens = unavailableConverter.convertList(cryptoCurrencies) - .map(TokensListItemUM::Token) - .toPersistentList(), - ).convert(Unit) - }.toPersistentList(), - totalTokensCount = totalTokensCount, - ) - } else { - TokenListUMData.TokenList( - tokensList = accountList.flatMap { (_, cryptoCurrencies) -> - unavailableConverter.convertList(cryptoCurrencies) - .map(TokensListItemUM::Token) - }.toPersistentList(), - totalTokensCount = totalTokensCount, - ) - }, - isBalanceHidden = isBalanceHidden, - warning = NotificationUM.Warning.SwapNoAvailablePair, - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/SwapMarketsListBatchFlowManager.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/SwapMarketsListBatchFlowManager.kt deleted file mode 100644 index 5df10fda71..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/SwapMarketsListBatchFlowManager.kt +++ /dev/null @@ -1,252 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.market - -import com.tangem.common.ui.markets.models.MarketsListItemUM -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.* -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.onramp.swap.availablepairs.market.converter.SwapMarketsTokenItemConverter -import com.tangem.pagination.Batch -import com.tangem.pagination.BatchAction -import com.tangem.pagination.PaginationStatus -import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* - -@Suppress("LongParameterList") -internal class SwapMarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, - private val batchFlowType: GetMarketsTokenListFlowUseCase.BatchFlowType, - private val order: TokenMarketListConfig.Order, - private val currentAppCurrency: Provider, - private val currentSearchText: Provider, - private val modelScope: CoroutineScope, - private val dispatchers: CoroutineDispatcherProvider, -) { - private val actionsFlow = MutableSharedFlow>() - private val updateStateJob = JobHolder() - - private val batchFlow = getMarketsTokenListFlowUseCase( - batchingContext = TokenListBatchingContext( - actionsFlow = actionsFlow, - coroutineScope = modelScope, - ), - batchFlowType = batchFlowType, - ) - - private val resultBatches = MutableStateFlow(ResultBatches()) - private val uiBatches = resultBatches.map { it.uiBatches } - - val uiItems: StateFlow> - get() = uiBatches - .map { batches -> - batches.asSequence() - .map { it.data } - .flatten() - .toImmutableList() - } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = persistentListOf(), - ) - - val isInInitialLoadingErrorState = batchFlow.state - .map { it.status is PaginationStatus.InitialLoadingError } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - - val isSearchNotFoundState = batchFlow.state - .map { batchListState -> - currentSearchText().isNullOrEmpty().not() && - batchListState.status is PaginationStatus.EndOfPagination && - batchListState.data.isEmpty() - } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - - val totalCount: StateFlow = batchFlow.state - .map { it.totalCount } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = null, - ) - - init { - batchFlow.state - .map { it.data } - .distinctUntilChanged { a, b -> - a.size == b.size && - a.map { it.key } == b.map { it.key } && - a.map { it.data }.flatten() == b.map { it.data }.flatten() - } - .onEach { - coroutineScope { - launch { - updateState(it) - }.saveIn(updateStateJob) - } - } - .flowOn(dispatchers.default) - .launchIn(modelScope) - } - - private suspend fun updateState(newList: List>>, forceUpdate: Boolean = false) = - withContext(dispatchers.default) { - resultBatches.update { resultBatches -> - val items = resultBatches.uiBatches - val previousList = resultBatches.processedItems - - val converter = SwapMarketsTokenItemConverter(appCurrency = currentAppCurrency()) - - if (newList.isEmpty()) { - return@update ResultBatches(processedItems = emptyList()) - } - - val isInitialLoading = - forceUpdate || previousList.isNullOrEmpty() || newList.first().key != previousList.first().key - - val outItems = if (isInitialLoading) { - newList.map { batch -> - Batch( - key = batch.key, - data = converter.convertList(batch.data), - ) - } - } else { - if (previousList.size != newList.size) { - val keysToAdd = newList.map { it.key }.subtract(previousList.map { it.key }.toSet()) - val newBatches = newList.filter { keysToAdd.contains(it.key) } - - items + newBatches.map { batch -> - Batch( - key = batch.key, - data = converter.convertList(batch.data), - ) - } - } else { - items.mapIndexed { batchIndex, batch -> - val prevBatch = previousList[batchIndex] - val newBatch = newList[batchIndex] - if (prevBatch == newBatch) return@mapIndexed batch - - Batch( - key = batch.key, - data = batch.data.mapIndexed { index, marketsListItemUM -> - val prevItem = prevBatch.data.getOrNull(index) - val newItem = newBatch.data.getOrNull(index) - if (prevItem != null && newItem != null) { - converter.update(prevItem, marketsListItemUM, newItem) - } else { - newItem?.let { converter.convert(it) } ?: marketsListItemUM - } - }, - ) - } - } - } - - currentCoroutineContext().ensureActive() - - ResultBatches( - uiBatches = outItems, - processedItems = newList, - ) - } - } - - fun reload(searchText: String? = null) { - modelScope.launch { - resultBatches.value = ResultBatches() - actionsFlow.emit( - BatchAction.Reload( - requestParams = TokenMarketListConfig( - fiatPriceCurrency = currentAppCurrency().code, - searchText = if (currentSearchText() == null) { - null - } else { - searchText ?: currentSearchText() - }, - priceChangeInterval = TokenMarketListConfig.Interval.H24, - order = order, - shouldNetworks = true, - ), - ), - ) - } - } - - fun loadMore() { - modelScope.launch { - actionsFlow.emit(BatchAction.LoadMore()) - } - } - - fun loadCharts(batchKeys: Set) { - if (batchKeys.isEmpty()) return - - modelScope.launch { - val currentData = batchFlow.state.value.data - val alreadyLoadedChartsBatchKeys = currentData - .filter { batch -> - val first = batch.data.firstOrNull() ?: return@filter false - first.tokenCharts.h24 != null - } - .map { it.key } - .toSet() - - val batchesKeysToLoad = batchKeys.minus(alreadyLoadedChartsBatchKeys) - - if (batchesKeysToLoad.isNotEmpty()) { - actionsFlow.emit( - BatchAction.UpdateBatches( - keys = batchesKeysToLoad, - updateRequest = TokenMarketUpdateRequest.UpdateChart( - interval = TokenMarketListConfig.Interval.H24, - currency = currentAppCurrency().code, - ), - async = true, - operationId = batchesKeysToLoad.toString() + "h24", - ), - ) - } - } - } - - fun getTokenMarketById(id: CryptoCurrency.RawID): TokenMarket? { - return batchFlow.state.value.data - .asSequence() - .flatMap { it.data } - .firstOrNull { it.id == id } - } - - fun getBatchKeysByItemIds(ids: List): Set { - val currentData = batchFlow.state.value.data - - return currentData - .filter { d -> d.data.any { ids.contains(it.id) } } - .map { it.key } - .toSet() - } - - private data class ResultBatches( - val uiBatches: List>> = emptyList(), - val processedItems: List>>? = null, - ) -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/converter/SwapMarketsTokenItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/converter/SwapMarketsTokenItemConverter.kt deleted file mode 100644 index 98a4c621b3..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/converter/SwapMarketsTokenItemConverter.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.market.converter - -import com.tangem.common.ui.charts.state.MarketChartData -import com.tangem.common.ui.charts.state.MarketChartRawData -import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter -import com.tangem.common.ui.charts.state.sorted -import com.tangem.common.ui.markets.models.MarketsListItemUM -import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated -import com.tangem.core.ui.R -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.format.bigdecimal.compact -import com.tangem.core.ui.format.bigdecimal.fiat -import com.tangem.core.ui.format.bigdecimal.format -import com.tangem.core.ui.format.bigdecimal.percent -import com.tangem.core.ui.format.bigdecimal.price -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.TokenMarket -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal -import java.math.RoundingMode - -internal class SwapMarketsTokenItemConverter( - private val appCurrency: AppCurrency, -) : Converter { - - private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = false) - - override fun convert(value: TokenMarket): MarketsListItemUM { - return MarketsListItemUM( - id = value.id, - name = value.name, - currencySymbol = value.symbol, - ratingPosition = value.marketRating?.toString(), - marketCap = value.getMarketCap(), - iconUrl = value.imageUrlLarge, - price = value.getCurrentPrice(), - trendPercentText = value.getTrendPercent(), - trendType = value.getTrendType(), - chartData = value.getChartData(), - isUnder100kMarketCap = value.isUnderMarketCapLimit, - stakingRate = value.yieldRate?.format { percent() }?.let { - resourceReference(R.string.markets_apy_placeholder, wrappedList(it)) - }, - updateTimestamp = value.updateTimestamp, - networks = value.networks?.map { network -> - MarketsListItemUM.Network( - networkId = network.networkId, - contractAddress = network.contractAddress, - decimalCount = network.decimalCount, - ) - }, - ) - } - - fun convertList(items: List): List = items.map(::convert) - - fun update(prev: TokenMarket, prevUI: MarketsListItemUM, new: TokenMarket): MarketsListItemUM { - require(prev.id == new.id) { - "Ids is not the same during update TokenMarket item: previousItem[${prev.id}] != newItem[${new.id}]" - } - - return prevUI.copy( - name = new.name, - currencySymbol = new.symbol, - ratingPosition = new.marketRating?.toString(), - marketCap = ifChanged(prev.marketCap, new.marketCap, prevUI.marketCap) { new.getMarketCap() }, - iconUrl = new.imageUrlLarge, - price = ifChanged(prev = prev.tokenQuotesShort, new = new.tokenQuotesShort, prevR = prevUI.price) { - new.getCurrentPrice(prev = prev) - }, - trendPercentText = ifChanged( - prev.tokenQuotesShort, - new.tokenQuotesShort, - prevUI.trendPercentText, - ) { new.getTrendPercent() }, - trendType = ifChanged(prev.tokenQuotesShort, new.tokenQuotesShort, prevUI.trendType) { new.getTrendType() }, - chartData = ifChanged(prev.tokenCharts, new.tokenCharts, prevUI.chartData) { new.getChartData() }, - ) - } - - private inline fun ifChanged(prev: T, new: T, prevR: R, force: Boolean = false, change: (T) -> R): R { - return if (force || prev != new) change(new) else prevR - } - - private fun TokenMarket.getMarketCap(): String? { - val value = marketCap?.takeIf { marketCap != BigDecimal.ZERO } ?: return null - - return value.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ).compact( - threeDigitsMethod = true, - ) - } - } - - private fun TokenMarket.getCurrentPrice(prev: TokenMarket? = null): MarketsListItemUM.Price { - val prevPrice = prev?.tokenQuotesShort?.currentPrice - - val priceText = tokenQuotesShort.currentPrice.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ).price() - } - - val changeType = if (prevPrice != null) { - if (tokenQuotesShort.currentPrice > prevPrice) { - PriceChangeType.UP - } else { - PriceChangeType.DOWN - } - } else { - null - } - - return MarketsListItemUM.Price( - text = priceText, - annotated = tokenQuotesShort.currentPrice.toMarketsListItemPriceAnnotated( - appCurrencyCode = appCurrency.code, - appCurrencySymbol = appCurrency.symbol, - ), - changeType = changeType, - fiatPrice = tokenQuotesShort.currentPrice, - ) - } - - private fun TokenMarket.getChartData(): MarketChartRawData? { - val chart = tokenCharts.h24 - - return chart?.let { ct -> - priceAndTimePointValuesConverter.convert( - MarketChartData.Data( - y = ct.priceY.toImmutableList(), - x = ct.timeStamps.map { it.toBigDecimal() }.toImmutableList(), - ).sorted(), - ) - } - } - - @Suppress("MagicNumber") - private fun TokenMarket.getTrendType(): PriceChangeType { - val percent = tokenQuotesShort.h24ChangePercent - val scaled = percent?.setScale(4, RoundingMode.HALF_UP) - return when { - scaled == null -> PriceChangeType.NEUTRAL - scaled > BigDecimal.ZERO -> PriceChangeType.UP - scaled < BigDecimal.ZERO -> PriceChangeType.DOWN - else -> PriceChangeType.NEUTRAL - } - } - - private fun TokenMarket.getTrendPercent(): String { - val percent = tokenQuotesShort.h24ChangePercent - return percent.format { percent() } - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/state/SwapMarketState.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/state/SwapMarketState.kt deleted file mode 100644 index 7e9abb3982..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/market/state/SwapMarketState.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.market.state - -import androidx.compose.runtime.Immutable -import com.tangem.common.ui.markets.models.MarketsListItemUM -import com.tangem.core.ui.R -import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.models.currency.CryptoCurrency -import kotlinx.collections.immutable.ImmutableList - -@Immutable -internal sealed class SwapMarketState { - - abstract val marketsTitle: TextReference - abstract val shouldAssetsCount: Boolean - - data class Content( - val items: ImmutableList, - val total: Int, - val loadMore: () -> Unit, - val onItemClick: (MarketsListItemUM) -> Unit, - val visibleIdsChanged: (List) -> Unit, - override val marketsTitle: TextReference, - override val shouldAssetsCount: Boolean, - ) : SwapMarketState() - - data class Loading( - override val marketsTitle: TextReference, - override val shouldAssetsCount: Boolean, - ) : SwapMarketState() - - data class LoadingError( - val onRetryClicked: () -> Unit, - override val marketsTitle: TextReference, - override val shouldAssetsCount: Boolean, - ) : SwapMarketState() - - data object SearchNothingFound : SwapMarketState() { - override val marketsTitle: TextReference = TextReference.Res(R.string.markets_common_title) - override val shouldAssetsCount: Boolean = true - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AddToPortfolioRoute.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AddToPortfolioRoute.kt deleted file mode 100644 index 559ef6eb09..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AddToPortfolioRoute.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.model - -import com.tangem.core.decompose.navigation.Route -import kotlinx.serialization.Serializable - -@Serializable -internal data object AddToPortfolioRoute : Route \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt deleted file mode 100644 index d87b8d3d0e..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt +++ /dev/null @@ -1,648 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.model - -import com.arkivanov.decompose.router.slot.SlotNavigation -import com.arkivanov.decompose.router.slot.activate -import com.arkivanov.decompose.router.slot.dismiss -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.common.ui.markets.models.MarketsListItemUM -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources -import com.tangem.core.analytics.models.event.SwapAnalyticsEvent -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.ui.components.fields.InputManager -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer -import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier -import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase -import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.core.utils.lceContent -import com.tangem.domain.core.utils.lceError -import com.tangem.domain.core.utils.lceLoading -import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase -import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.markets.TokenMarketListConfig -import com.tangem.domain.markets.toSerializableParam -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.account.AccountStatus -import com.tangem.domain.models.account.filterCryptoPortfolio -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.tokenlist.TokenList -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.wallets.usecase.GetWalletsUseCase -import com.tangem.feature.swap.domain.GetAvailablePairsUseCase -import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo -import com.tangem.feature.swap.domain.models.domain.SwapPairLeast -import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent -import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetErrorWarningTransformer -import com.tangem.features.onramp.swap.availablepairs.entity.transformers.SetNoAvailablePairsTransformer -import com.tangem.features.onramp.swap.availablepairs.market.SwapMarketsListBatchFlowManager -import com.tangem.features.onramp.swap.availablepairs.market.state.SwapMarketState -import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM -import com.tangem.features.onramp.swap.entity.AccountCurrencyUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUM -import com.tangem.features.onramp.tokenlist.entity.TokenListUMController -import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.SetLoadingAccountTokenListTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.SetNothingToFoundStateTransformer -import com.tangem.features.onramp.tokenlist.entity.transformer.UpdateAccountTokenListTransformer -import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory -import com.tangem.features.onramp.utils.ClearSearchBarTransformer -import com.tangem.features.onramp.utils.UpdateSearchBarActiveStateTransformer -import com.tangem.features.onramp.utils.UpdateSearchBarCallbacksTransformer -import com.tangem.features.onramp.utils.UpdateSearchQueryTransformer -import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runSuspendCatching -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch -import javax.inject.Inject -import com.tangem.core.ui.R as CoreUiR - -private typealias AvailablePairsState = Lce> - -@Suppress("LongParameterList", "LargeClass") -internal class AvailableSwapPairsModel @Inject constructor( - paramsContainer: ParamsContainer, - override val dispatchers: CoroutineDispatcherProvider, - private val analyticsEventHandler: AnalyticsEventHandler, - private val tokenListUMController: TokenListUMController, - private val searchManager: InputManager, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val getAvailablePairsUseCase: GetAvailablePairsUseCase, - private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, - private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, - private val getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, - private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory, - private val excludedBlockchains: ExcludedBlockchains, - private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, - getWalletsUseCase: GetWalletsUseCase, -) : Model() { - - val state: StateFlow = tokenListUMController.state - - private val params: AvailableSwapPairsComponent.Params = paramsContainer.require() - private val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } - private val allUserWallets = getWalletsUseCase.invokeSync() - - val bottomSheetNavigation: SlotNavigation = SlotNavigation() - val addToPortfolioManager: AddToPortfolioManager = addToPortfolioManagerFactory - .create( - scope = modelScope, - analyticsParams = AddToPortfolioManager.AnalyticsParams(source = ScreensSources.Swap.value), - settings = AddToPortfolioManager.Settings.ChooseToken, - ) - - private val accountListFlow = getAccountListUseCaseFlow() - private val availablePairsByNetworkFlow = MutableStateFlow>(emptyMap()) - - private val selectedAppCurrencyFlow: StateFlow = getSelectedAppCurrencyUseCase.invokeOrDefault() - .stateIn(scope = modelScope, started = SharingStarted.Eagerly, initialValue = AppCurrency.Default) - private val refreshPairsTrigger = MutableSharedFlow() - private val searchQueryStateForMarkets = MutableStateFlow("") - private val visibleMarketItemIds = MutableStateFlow>(emptyList()) - - private val defaultMarketsListManager by lazy { - SwapMarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase, - batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main, - order = TokenMarketListConfig.Order.Trending, - currentAppCurrency = Provider { selectedAppCurrencyFlow.value }, - currentSearchText = Provider { null }, - modelScope = modelScope, - dispatchers = dispatchers, - ) - } - - private val searchMarketsListManager by lazy { - SwapMarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase, - batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Search, - order = TokenMarketListConfig.Order.ByRating, - currentAppCurrency = Provider { selectedAppCurrencyFlow.value }, - currentSearchText = Provider { searchQueryStateForMarkets.value }, - modelScope = modelScope, - dispatchers = dispatchers, - ) - } - - private val visibleDefaultMarketItemIds = MutableStateFlow>(emptyList()) - - init { - subscribeOnUpdateState() - - initializeSearchBarCallbacks() - subscribeOnSelectedStatusChange() - subscribeOnAvailablePairsUpdates() - - subscribeOnMarketsUpdates() - subscribeOnVisibleMarketItems() - addToPortfolioManager.onDismiss.receiveAsFlow() - .onEach { bottomSheetNavigation.dismiss() } - .launchIn(modelScope) - addToPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { result -> onTokenAddedToPortfolio(result.addedCurrency.currency) } - .launchIn(modelScope) - } - - private fun getAccountListUseCaseFlow(): SharedFlow> { - return singleAccountStatusListSupplier(SingleAccountStatusListProducer.Params(params.userWalletId)) - .distinctUntilChanged() - .mapNotNull { accountStatusList -> - accountStatusList.accountStatuses.filter { - it is AccountStatus.CryptoPortfolio && it.tokenList !is TokenList.Empty - } - } - .flowOn(dispatchers.default) - .shareIn(scope = modelScope, started = SharingStarted.Eagerly, replay = 1) - } - - private fun subscribeOnSelectedStatusChange() { - params.selectedStatus - .filter { it == null } - .onEach { clearSearchState() } - .launchIn(modelScope) - } - - private fun initializeSearchBarCallbacks() { - tokenListUMController.update( - transformer = UpdateSearchBarCallbacksTransformer( - onQueryChange = ::onSearchQueryChange, - onActiveChange = ::onSearchBarActiveChange, - ), - ) - } - - private fun subscribeOnUpdateState() { - combine( - flow = getAccountsAndModeFlow(), - flow2 = getAppCurrencyAndBalanceHidingFlow(), - flow3 = params.selectedStatus, - flow4 = searchManager.query, - flow5 = availablePairsByNetworkFlow - .map { it[params.selectedStatus.value?.toLeastTokenInfo()] } - .distinctUntilChanged(), - ) { accountListAndMode, appCurrencyAndBalanceHiding, selectedStatus, query, availablePairsState -> - val (accountList, isAccountsMode) = accountListAndMode - availablePairsState?.fold( - ifLoading = { - SetLoadingAccountTokenListTransformer( - appCurrency = appCurrencyAndBalanceHiding.first, - accountList = accountList, - isAccountsMode = isAccountsMode, - ) - }, - ifContent = { pairs -> - handleContentState( - appCurrencyAndBalanceHiding = appCurrencyAndBalanceHiding, - accountList = accountList, - selectedStatus = selectedStatus, - query = query, - availablePairs = pairs, - isAccountsMode = isAccountsMode, - ) - }, - ifError = { throwable -> - handleErrorState( - cause = throwable, - networkInfo = params.selectedStatus.value?.toLeastTokenInfo(), - accountList = accountList, - ) - }, - ) ?: SetLoadingAccountTokenListTransformer( - appCurrency = appCurrencyAndBalanceHiding.first, - accountList = accountList, - isAccountsMode = isAccountsMode, - ) - } - .onEach(tokenListUMController::update) - .flowOn(dispatchers.default) - .launchIn(modelScope) - } - - private fun handleContentState( - appCurrencyAndBalanceHiding: Pair, - accountList: List, - selectedStatus: CryptoCurrencyStatus?, - query: String, - availablePairs: List, - isAccountsMode: Boolean, - ): TokenListUMTransformer { - val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding - - val filterByQueryAccountList: Map> = accountList - .filterCryptoPortfolio() - .associate { accountStatus -> - val statuses = accountStatus.tokenList.flattenCurrencies() - .filterNot { status -> - status.currency.network.rawId == selectedStatus?.currency?.network?.rawId && - status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress - } - .filterByQuery(query = query) - - accountStatus.account to statuses - } - .filterValues { it.isNotEmpty() } - - if (availablePairs.isEmpty()) { - return SetNoAvailablePairsTransformer( - appCurrency = appCurrency, - accountList = filterByQueryAccountList, - unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header), - isBalanceHidden = isBalanceHidden, - isAccountsMode = isAccountsMode, - ) - } - - return if (query.isNotEmpty() && filterByQueryAccountList.isEmpty()) { - SetNothingToFoundStateTransformer( - isBalanceHidden = isBalanceHidden, - emptySearchMessageReference = resourceReference( - id = R.string.action_buttons_swap_empty_search_message, - ), - ) - } else { - UpdateAccountTokenListTransformer( - appCurrency = appCurrency, - onItemClick = ::onPortfolioTokenClick, - accountList = filterByQueryAccountList.filterByAvailability(availablePairs = availablePairs), - isBalanceHidden = isBalanceHidden, - unavailableErrorText = resourceReference(R.string.tokens_list_unavailable_to_swap_source_header), - isAccountsMode = isAccountsMode, - ) - } - } - - private fun handleErrorState( - cause: Throwable, - networkInfo: LeastTokenInfo?, - accountList: List, - ): SetErrorWarningTransformer { - return SetErrorWarningTransformer( - cause = cause, - onRefresh = { - modelScope.launch { - if (networkInfo != null) { - accountList.filterCryptoPortfolio() - .forEach { (_, currencies) -> - updateAvailablePairs(networkInfo, currencies.flattenCurrencies()) - } - } - } - }, - ) - } - - private fun subscribeOnAvailablePairsUpdates() { - modelScope.launch { - combine( - params.selectedStatus.filterNotNull(), - refreshPairsTrigger - .onEach { availablePairsByNetworkFlow.value = emptyMap() } - .onStart { emit(Unit) }, - ) { status, _ -> status } - .collectLatest { selectedStatus -> - val networkInfo = selectedStatus.toLeastTokenInfo() - - val isAlreadyLoaded = availablePairsByNetworkFlow.value[networkInfo]?.isContent() == true - if (isAlreadyLoaded) return@collectLatest - - val accountList = accountListFlow.firstOrNull() ?: return@collectLatest - updateAvailablePairs( - networkInfo = networkInfo, - statuses = accountList.filterCryptoPortfolio() - .flatMap { accountStatus -> - accountStatus.flattenCurrencies() - }.toSet().toList(), - ) - } - } - } - - private suspend fun updateAvailablePairs(networkInfo: LeastTokenInfo, statuses: List) { - runSuspendCatching { - availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = lceLoading()) - - getAvailablePairsUseCase( - userWallet = userWallet, - initialCurrency = networkInfo, - currencies = statuses.map(CryptoCurrencyStatus::currency), - ) - } - .onSuccess { pairs -> - availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = pairs.lceContent()) - } - .onFailure { cause -> - availablePairsByNetworkFlow.update(networkInfo = networkInfo, state = cause.lceError()) - } - } - - private fun MutableStateFlow>.update( - networkInfo: LeastTokenInfo, - state: AvailablePairsState, - ) { - update { map -> - map.toMutableMap().apply { - this[networkInfo] = state - } - } - } - - private fun getAppCurrencyAndBalanceHidingFlow(): Flow> { - return combine( - flow = getSelectedAppCurrencyUseCase.invokeOrDefault(), - flow2 = getBalanceHidingSettingsUseCase.isBalanceHidden(), - transform = ::Pair, - ) - } - - private fun getAccountsAndModeFlow(): Flow, Boolean>> { - return combine( - flow = accountListFlow.distinctUntilChanged(), - flow2 = isAccountsModeEnabledUseCase().distinctUntilChanged(), - transform = ::Pair, - ) - } - - private fun onSearchQueryChange(newQuery: String) { - if (state.value.searchBarUM.query == newQuery) return - - modelScope.launch { - tokenListUMController.update(transformer = UpdateSearchQueryTransformer(newQuery)) - - searchManager.update(newQuery) - - searchQueryStateForMarkets.value = newQuery - } - } - - private fun onSearchBarActiveChange(isActive: Boolean) { - tokenListUMController.update( - transformer = UpdateSearchBarActiveStateTransformer( - isActive = isActive, - placeHolder = resourceReference(id = R.string.common_search), - ), - ) - } - - private fun List.filterByQuery(query: String): List { - return filter { status -> - status.currency.name.contains(other = query, ignoreCase = true) || - status.currency.symbol.contains(other = query, ignoreCase = true) - } - } - - private fun Map>.filterByAvailability( - availablePairs: List, - ): List { - return map { (account, currencies) -> - AccountAvailabilityUM( - account = account, - currencyList = currencies.map { status -> - val isAvailable = availablePairs.map(SwapPairLeast::to).contains(status.toLeastTokenInfo()) - - val isAvailableToSwap = isAvailable && - status.value !is CryptoCurrencyStatus.MissedDerivation && - status.value !is CryptoCurrencyStatus.Unreachable && - !status.currency.isCustom - - AccountCurrencyUM( - cryptoCurrencyStatus = status, - isAvailable = isAvailableToSwap, - ) - }, - ) - } - } - - private fun onPortfolioTokenClick(tokenItem: TokenItemState, status: CryptoCurrencyStatus) { - analyticsEventHandler.send( - SwapAnalyticsEvent.TokenSelected( - token = status.currency.symbol, - source = ScreensSources.Portfolio, - isSearched = state.value.searchBarUM.query.isNotEmpty(), - ), - ) - clearSearchState() - params.onTokenClick(tokenItem, status) - } - - private fun clearSearchState() { - tokenListUMController.update( - transformer = ClearSearchBarTransformer( - placeHolder = resourceReference(id = R.string.common_search), - ), - ) - modelScope.launch { - searchManager.update("") - } - searchQueryStateForMarkets.value = "" - } - - private fun CryptoCurrencyStatus.toLeastTokenInfo(): LeastTokenInfo { - return LeastTokenInfo( - contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress ?: "0", - network = currency.network.rawId, - ) - } - - @OptIn(ExperimentalCoroutinesApi::class) - private fun subscribeOnMarketsUpdates() { - searchQueryStateForMarkets - .map { it.isEmpty() } - .distinctUntilChanged() - .flatMapLatest { isDefaultMode -> - if (isDefaultMode) { - visibleMarketItemIds.value = emptyList() - createDefaultMarketsFlow() - } else { - visibleDefaultMarketItemIds.value = emptyList() - createSearchMarketsFlow() - } - } - .onEach { marketsState -> - tokenListUMController.update { it.copy(marketsState = marketsState) } - } - .flowOn(dispatchers.main) - .launchIn(modelScope) - - searchQueryStateForMarkets - .onEach { searchQuery -> - if (searchQuery.isNotEmpty()) { - searchMarketsListManager.reload(searchQuery) - } - } - .launchIn(modelScope) - - params.selectedStatus - .filterNotNull() - .take(1) - .onEach { defaultMarketsListManager.reload() } - .launchIn(modelScope) - } - - private fun createDefaultMarketsFlow(): Flow { - val marketsTitle = TextReference.Res(CoreUiR.string.feed_trending_now) - return combine( - defaultMarketsListManager.uiItems, - defaultMarketsListManager.isInInitialLoadingErrorState, - defaultMarketsListManager.totalCount, - ) { uiItems, isError, total -> - when { - isError -> SwapMarketState.LoadingError( - onRetryClicked = { defaultMarketsListManager.reload() }, - marketsTitle = marketsTitle, - shouldAssetsCount = false, - ) - uiItems.isEmpty() -> SwapMarketState.Loading( - marketsTitle = marketsTitle, - shouldAssetsCount = false, - ) - else -> SwapMarketState.Content( - items = uiItems, - loadMore = { defaultMarketsListManager.loadMore() }, - onItemClick = { item -> addToPortfolioItem(item) }, - visibleIdsChanged = { visibleDefaultMarketItemIds.value = it }, - total = total ?: uiItems.size, - marketsTitle = marketsTitle, - shouldAssetsCount = false, - ) - } - } - } - - private fun createSearchMarketsFlow(): Flow { - val marketsTitle = TextReference.Res(CoreUiR.string.markets_common_title) - return combine( - flow = searchMarketsListManager.uiItems, - flow2 = searchMarketsListManager.isInInitialLoadingErrorState, - flow3 = searchMarketsListManager.isSearchNotFoundState, - flow4 = searchMarketsListManager.totalCount, - ) { uiItems, isError, isSearchNotFound, total -> - when { - isError -> SwapMarketState.LoadingError( - onRetryClicked = { - searchMarketsListManager.reload(searchQueryStateForMarkets.value) - }, - marketsTitle = marketsTitle, - shouldAssetsCount = true, - ) - isSearchNotFound -> SwapMarketState.SearchNothingFound - uiItems.isEmpty() -> SwapMarketState.Loading( - marketsTitle = marketsTitle, - shouldAssetsCount = true, - ) - else -> SwapMarketState.Content( - items = uiItems, - loadMore = { searchMarketsListManager.loadMore() }, - onItemClick = { item -> addToPortfolioItem(item) }, - visibleIdsChanged = { visibleMarketItemIds.value = it }, - total = total ?: uiItems.size, - marketsTitle = marketsTitle, - shouldAssetsCount = true, - ) - } - } - } - - private fun onTokenAddedToPortfolio(addedToken: CryptoCurrency) { - modelScope.launch { - bottomSheetNavigation.dismiss() - analyticsEventHandler.send( - SwapAnalyticsEvent.TokenSelected( - token = addedToken.symbol, - source = ScreensSources.Markets, - isSearched = state.value.searchBarUM.query.isNotEmpty(), - ), - ) - - clearSearchState() - - // Trigger re-fetch of available pairs (clears cache + re-enters collectLatest) - refreshPairsTrigger.emit(Unit) - - // Wait for the added token status to become Loaded - val addedTokenStatus = getAccountCurrencyStatusUseCase(params.userWalletId, addedToken) - .firstOrNull { it.status.value is CryptoCurrencyStatus.Loaded } - ?.status - ?: return@launch - - // Convert to TokenItemState and trigger token selection → navigates to swap - val converter = OnrampTokenItemStateConverterFactory.createAvailableItemConverter( - appCurrency = selectedAppCurrencyFlow.value, - onItemClick = params.onTokenClick, - ) - params.onTokenClick(converter.convert(addedTokenStatus), addedTokenStatus) - } - } - - private fun addToPortfolioItem(item: MarketsListItemUM) { - val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id) - ?: searchMarketsListManager.getTokenMarketById(item.id) - ?: return - - val param = tokenMarket.toSerializableParam() - val hasOnlyHotWallets = allUserWallets.all { it is UserWallet.Hot } - - 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.setTokenNetworks(networks) - addToPortfolioManager.setTokenParams(param) - - bottomSheetNavigation.activate(AddToPortfolioRoute) - } - - private fun subscribeOnVisibleMarketItems() { - modelScope.launch { - visibleMarketItemIds.mapNotNull { rawIds -> - if (rawIds.isNotEmpty()) { - searchMarketsListManager.getBatchKeysByItemIds(rawIds) - } else { - null - } - }.distinctUntilChanged().collectLatest { visibleBatchKeys -> - searchMarketsListManager.loadCharts(visibleBatchKeys) - } - } - modelScope.launch { - visibleDefaultMarketItemIds.mapNotNull { rawIds -> - if (rawIds.isNotEmpty()) { - defaultMarketsListManager.getBatchKeysByItemIds(rawIds) - } else { - null - } - }.distinctUntilChanged().collectLatest { visibleBatchKeys -> - defaultMarketsListManager.loadCharts(visibleBatchKeys) - } - } - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/ui/SwapMarketsListItems.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/ui/SwapMarketsListItems.kt deleted file mode 100644 index 18600da888..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/ui/SwapMarketsListItems.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.tangem.features.onramp.swap.availablepairs.ui - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.withStyle -import com.tangem.common.ui.markets.MarketsListItem -import com.tangem.common.ui.markets.MarketsListItemPlaceholder -import com.tangem.core.ui.R -import com.tangem.core.ui.components.UnableToLoadData -import com.tangem.core.ui.decorations.roundedShapeItemDecoration -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.onramp.swap.availablepairs.market.state.SwapMarketState - -private const val LOADING_PLACEHOLDERS_COUNT = 20 - -internal fun LazyListScope.swapMarketsListItems(state: SwapMarketState) { - item(key = "markets_title") { - val totalCount = (state as? SwapMarketState.Content)?.total - Text( - text = buildAnnotatedString { - append(state.marketsTitle.resolveReference()) - if (totalCount != null) { - withStyle(SpanStyle(color = TangemTheme.colors.text.tertiary)) { - append(" $totalCount") - } - } - }, - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16) - .padding(top = TangemTheme.dimens.spacing24, bottom = TangemTheme.dimens.spacing12), - ) - } - - when (state) { - is SwapMarketState.Loading -> { - items(count = LOADING_PLACEHOLDERS_COUNT, key = { "market_placeholder_$it" }) { - MarketsListItemPlaceholder() - } - } - is SwapMarketState.LoadingError -> { - item(key = "market_loading_error") { - LoadingErrorItem( - modifier = Modifier.fillParentMaxWidth(), - onTryAgain = state.onRetryClicked, - ) - } - } - SwapMarketState.SearchNothingFound -> { - item(key = "market_not_found") { - SearchNothingFoundText( - modifier = Modifier.fillParentMaxWidth(), - ) - } - } - is SwapMarketState.Content -> { - itemsIndexed( - items = state.items, - key = { _, item -> item.getComposeKey() }, - ) { index, item -> - MarketsListItem( - model = item, - onClick = { state.onItemClick(item) }, - modifier = Modifier.roundedShapeItemDecoration( - currentIndex = index, - lastIndex = state.items.lastIndex, - backgroundColor = TangemTheme.colors.background.action, - ), - ) - } - } - } -} - -@Composable -private fun LoadingErrorItem(onTryAgain: () -> Unit, modifier: Modifier = Modifier) { - Box( - modifier - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing12, - ), - contentAlignment = Alignment.Center, - ) { - UnableToLoadData(onRetryClick = onTryAgain) - } -} - -@Composable -private fun SearchNothingFoundText(modifier: Modifier = Modifier) { - Box( - modifier = modifier.padding(TangemTheme.dimens.spacing16), - contentAlignment = Alignment.Center, - ) { - Text( - text = stringResourceSafe(R.string.markets_search_token_no_result_title), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt deleted file mode 100644 index 059c28a82a..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensComponentModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onramp.swap.di - -import com.tangem.features.onramp.component.SwapSelectTokensComponent -import com.tangem.features.onramp.swap.DefaultSwapSelectTokensComponent -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface SwapSelectTokensComponentModule { - - @Binds - @Singleton - fun bindSwapSelectTokensComponentFactory( - factory: DefaultSwapSelectTokensComponent.Factory, - ): SwapSelectTokensComponent.Factory -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt deleted file mode 100644 index 01b1201354..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/di/SwapSelectTokensModelModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.onramp.swap.di - -import com.tangem.core.decompose.di.ModelComponent -import com.tangem.core.decompose.model.Model -import com.tangem.features.onramp.swap.model.SwapSelectTokensModel -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(ModelComponent::class) -internal interface SwapSelectTokensModelModule { - - @Binds - @IntoMap - @ClassKey(SwapSelectTokensModel::class) - fun bindOnrampTokenListModel(model: SwapSelectTokensModel): Model -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt deleted file mode 100644 index 1d5877f437..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.features.onramp.swap.entity - -import androidx.compose.runtime.Immutable -import com.tangem.common.ui.account.AccountIconUM -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.extensions.TextReference - -/** - * Exchange card UI model - * -[REDACTED_AUTHOR] - */ -internal sealed interface ExchangeCardUM { - - /** Title reference */ - val titleUM: TitleUM - - /** Remove button UI model */ - val removeButtonUM: RemoveButtonUM? - - /** - * Empty state - * - * @property titleUM title reference - * @property subtitleReference empty token subtitle reference - */ - data class Empty( - override val titleUM: TitleUM, - val subtitleReference: TextReference, - ) : ExchangeCardUM { - - override val removeButtonUM: RemoveButtonUM? = null - } - - /** - * Filled - * - * @property titleUM title reference - * @property removeButtonUM remove button UI model - * @property tokenItemState token item state - */ - data class Filled( - override val titleUM: TitleUM, - override val removeButtonUM: RemoveButtonUM?, - val tokenItemState: TokenItemState, - ) : ExchangeCardUM - - data class RemoveButtonUM(val onClick: () -> Unit) - - @Immutable - sealed interface TitleUM { - - data class Text( - val title: TextReference, - ) : TitleUM - - data class Account( - val prefixText: TextReference, - val name: TextReference, - val icon: AccountIconUM.CryptoPortfolio, - ) : TitleUM - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt deleted file mode 100644 index 8adcb4725d..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensController.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.features.onramp.swap.entity - -import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeFrom -import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update -import com.tangem.utils.logging.TangemLogger -import javax.inject.Inject - -/** - * [SwapSelectTokensUM] controller - * -[REDACTED_AUTHOR] - */ -internal class SwapSelectTokensController @Inject constructor() { - - val state: StateFlow - field = MutableStateFlow( - value = SwapSelectTokensUM( - onBackClick = {}, - exchangeFrom = createEmptyExchangeFrom(), - exchangeTo = createEmptyExchangeTo(), - isBalanceHidden = false, - ), - ) - - fun update(transform: (SwapSelectTokensUM) -> SwapSelectTokensUM) { - TangemLogger.d("Applying non-name transformation") - state.update(transform) - } - - fun update(transformer: SwapSelectTokensUMTransformer) { - TangemLogger.d("Applying ${transformer::class.simpleName ?: "null"}") - state.update(transformer::transform) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt deleted file mode 100644 index 1bdf9b047a..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUM.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.features.onramp.swap.entity - -/** - * Swap select tokens UI model - * - * @property onBackClick callback is called when back button is clicked - * @property exchangeFrom exchange "from" card UI model - * @property exchangeTo exchange "to" card UI model - * -[REDACTED_AUTHOR] - */ -internal data class SwapSelectTokensUM( - val onBackClick: () -> Unit, - val exchangeFrom: ExchangeCardUM, - val exchangeTo: ExchangeCardUM, - val isBalanceHidden: Boolean, -) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt deleted file mode 100644 index fb2f9ca537..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/SwapSelectTokensUMTransformer.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.onramp.swap.entity - -import com.tangem.utils.transformer.Transformer - -/** - * Base [SwapSelectTokensUM] transformer - * -[REDACTED_AUTHOR] - */ -internal interface SwapSelectTokensUMTransformer : Transformer \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt deleted file mode 100644 index 828f9d6ad0..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedFromTokenTransformer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.features.onramp.swap.entity.transformer - -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer -import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeFrom -import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo - -/** - * Transformer for removing selected "from" token - * -[REDACTED_AUTHOR] - */ -internal object RemoveSelectedFromTokenTransformer : SwapSelectTokensUMTransformer { - - override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { - return prevState.copy( - exchangeFrom = createEmptyExchangeFrom(), - exchangeTo = createEmptyExchangeTo(), - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedToTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedToTokenTransformer.kt deleted file mode 100644 index 971bf36fd8..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/RemoveSelectedToTokenTransformer.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.features.onramp.swap.entity.transformer - -import com.tangem.features.onramp.swap.entity.ExchangeCardUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer -import com.tangem.features.onramp.swap.entity.utils.createEmptyExchangeTo - -/** - * Transformer for removing selected "to" token - * -[REDACTED_AUTHOR] - */ -internal class RemoveSelectedToTokenTransformer( - private val onRemoveFromTokenClick: () -> Unit, -) : SwapSelectTokensUMTransformer { - - override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { - return prevState.copy( - exchangeFrom = prevState.exchangeFrom.showRemoveButton(onClick = onRemoveFromTokenClick), - exchangeTo = createEmptyExchangeTo(), - ) - } - - private fun ExchangeCardUM.showRemoveButton(onClick: () -> Unit): ExchangeCardUM { - return (this as? ExchangeCardUM.Filled) - ?.copy(removeButtonUM = ExchangeCardUM.RemoveButtonUM(onClick = onClick)) - ?: this - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt deleted file mode 100644 index 3ca1f8776b..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectFromTokenTransformer.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.features.onramp.swap.entity.transformer - -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.domain.models.account.Account -import com.tangem.features.onramp.swap.entity.ExchangeCardUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer -import com.tangem.features.onramp.swap.entity.utils.toFilled - -/** - * Transformer for selecting "from" token - * - * @property selectedTokenItemState token item state - * @property onRemoveClick callback is called when remove button is clicked - * -[REDACTED_AUTHOR] - */ -internal class SelectFromTokenTransformer( - private val selectedTokenItemState: TokenItemState, - private val onRemoveClick: () -> Unit, - private val account: Account.CryptoPortfolio?, - private val isAccountsMode: Boolean, -) : SwapSelectTokensUMTransformer { - - override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { - return prevState.copy( - exchangeFrom = prevState.exchangeFrom.toFilled( - selectedTokenItemState = selectedTokenItemState, - removeButtonUM = ExchangeCardUM.RemoveButtonUM(onClick = onRemoveClick), - account = account, - isAccountsMode = isAccountsMode, - isFromCurrency = true, - ), - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt deleted file mode 100644 index 8699cd9e56..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/transformer/SelectToTokenTransformer.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.features.onramp.swap.entity.transformer - -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.domain.models.account.Account -import com.tangem.features.onramp.swap.entity.ExchangeCardUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUMTransformer -import com.tangem.features.onramp.swap.entity.utils.toFilled - -/** - * Transformer for selecting "to" token - * - * @property selectedTokenItemState token item state - * -[REDACTED_AUTHOR] - */ -internal class SelectToTokenTransformer( - private val selectedTokenItemState: TokenItemState, - private val isAccountsMode: Boolean, - private val account: Account.CryptoPortfolio?, -) : SwapSelectTokensUMTransformer { - - override fun transform(prevState: SwapSelectTokensUM): SwapSelectTokensUM { - return prevState.copy( - exchangeFrom = prevState.exchangeFrom.hideRemoveButton(), - exchangeTo = prevState.exchangeTo.toFilled( - selectedTokenItemState = selectedTokenItemState, - isAccountsMode = isAccountsMode, - account = account, - isFromCurrency = false, - ), - ) - } - - private fun ExchangeCardUM.hideRemoveButton(): ExchangeCardUM { - return (this as? ExchangeCardUM.Filled)?.copy(removeButtonUM = null) ?: this - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt deleted file mode 100644 index 892b627a0c..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.features.onramp.swap.entity.utils - -import com.tangem.common.ui.account.CryptoPortfolioIconConverter -import com.tangem.common.ui.account.toUM -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.models.account.Account -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.entity.ExchangeCardUM - -/** Create empty exchange "from" card */ -internal fun createEmptyExchangeFrom(): ExchangeCardUM.Empty { - return ExchangeCardUM.Empty( - titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)), - subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap), - ) -} - -/** Create empty exchange "to" card */ -internal fun createEmptyExchangeTo(): ExchangeCardUM.Empty { - return ExchangeCardUM.Empty( - titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_to_title)), - subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_receive), - ) -} - -/** - * Convert from [ExchangeCardUM] to [ExchangeCardUM.Filled] - * - * @param selectedTokenItemState token item state - * @param removeButtonUM remove button UI model - */ -internal fun ExchangeCardUM.toFilled( - selectedTokenItemState: TokenItemState, - account: Account.CryptoPortfolio?, - isAccountsMode: Boolean, - isFromCurrency: Boolean, - removeButtonUM: ExchangeCardUM.RemoveButtonUM? = null, -): ExchangeCardUM.Filled { - return ExchangeCardUM.Filled( - titleUM = if (account != null && isAccountsMode) { - ExchangeCardUM.TitleUM.Account( - prefixText = if (isFromCurrency) { - resourceReference(R.string.common_from) - } else { - resourceReference(R.string.common_to) - }, - name = account.accountName.toUM().value, - icon = CryptoPortfolioIconConverter.convert(account.icon), - ) - } else { - titleUM - }, - tokenItemState = selectedTokenItemState, - removeButtonUM = removeButtonUM, - ) -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt deleted file mode 100644 index 697f08be5a..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/model/SwapSelectTokensModel.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.tangem.features.onramp.swap.model - -import com.tangem.common.routing.AppRoute -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.decompose.navigation.Router -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase -import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.features.onramp.component.SwapSelectTokensComponent -import com.tangem.features.onramp.swap.entity.SwapSelectTokensController -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedFromTokenTransformer -import com.tangem.features.onramp.swap.entity.transformer.RemoveSelectedToTokenTransformer -import com.tangem.features.onramp.swap.entity.transformer.SelectFromTokenTransformer -import com.tangem.features.onramp.swap.entity.transformer.SelectToTokenTransformer -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout -import javax.inject.Inject - -@Suppress("LongParameterList") -internal class SwapSelectTokensModel @Inject constructor( - paramsContainer: ParamsContainer, - override val dispatchers: CoroutineDispatcherProvider, - private val controller: SwapSelectTokensController, - private val router: Router, - private val analyticsEventHandler: AnalyticsEventHandler, - private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, - private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, -) : Model() { - - val state: StateFlow = controller.state - - val fromCurrencyStatus: StateFlow - field = MutableStateFlow(value = null) - - private val _toCurrencyStatus = MutableStateFlow(value = null) - - private val params = paramsContainer.require() - - private var isAccountsMode: Boolean = false - - init { - controller.update { it.copy(onBackClick = ::onBackClick) } - - subscribeOnAccountsMode() - subscribeOnBalanceHidingSettings() - } - - /** - * Select "from" token - * - * @param selectedTokenItemState selected token item state - * @param status crypto currency status - */ - fun selectFromToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { - analyticsEventHandler.send( - event = MainScreenAnalyticsEvent.SwapTokenClicked(currencySymbol = status.currency.symbol), - ) - - fromCurrencyStatus.value = status - - modelScope.launch { - controller.update( - transformer = SelectFromTokenTransformer( - selectedTokenItemState = selectedTokenItemState, - onRemoveClick = ::onRemoveFromTokenClick, - isAccountsMode = isAccountsMode, - account = getAccountCurrencyStatusUseCase.invokeSync( - userWalletId = params.userWalletId, - currency = status.currency, - ).getOrNull()?.account, - ), - ) - } - } - - /** - * Select "to" token - * - * @param selectedTokenItemState selected token item state - * @param status crypto currency status - */ - fun selectToToken(selectedTokenItemState: TokenItemState, status: CryptoCurrencyStatus) { - analyticsEventHandler.send( - event = MainScreenAnalyticsEvent.ReceiveTokenClicked(currencySymbol = status.currency.symbol), - ) - - modelScope.launch { - _toCurrencyStatus.value = status - - controller.update( - transformer = SelectToTokenTransformer( - selectedTokenItemState = selectedTokenItemState, - isAccountsMode = isAccountsMode, - account = getAccountCurrencyStatusUseCase.invokeSync( - userWalletId = params.userWalletId, - currency = status.currency, - ).getOrNull()?.account, - ), - ) - - // require some delay to show state with selected "from" and "to" tokens - delay(timeMillis = 500) - - router.push( - route = AppRoute.Swap( - fromCryptoCurrency = requireNotNull(fromCurrencyStatus.value).currency, - userWalletId = params.userWalletId, - screenSource = AnalyticsParam.ScreensSources.Main.value, - ), - onComplete = { - modelScope.launch { - withTimeout(timeMillis = 500) { - // Return a state with selected only "from" token - removeSelectedToToken() - } - } - }, - ) - } - } - - private fun subscribeOnBalanceHidingSettings() { - getBalanceHidingSettingsUseCase() - .map { it.isBalanceHidden } - .distinctUntilChanged() - .onEach { - controller.update { state -> state.copy(isBalanceHidden = it) } - } - .flowOn(dispatchers.mainImmediate) - .launchIn(modelScope) - } - - private fun subscribeOnAccountsMode() { - isAccountsModeEnabledUseCase() - .distinctUntilChanged() - .onEach { - isAccountsMode = it - } - .flowOn(dispatchers.default) - .launchIn(modelScope) - } - - private fun onBackClick() { - analyticsEventHandler.send( - event = MainScreenAnalyticsEvent.ButtonClose(source = AnalyticsParam.ScreensSources.Swap), - ) - - router.pop() - } - - private fun onRemoveFromTokenClick() { - val currencySymbol = requireNotNull(fromCurrencyStatus.value?.currency?.symbol) { - "Token was not selected" - } - - analyticsEventHandler.send( - event = MainScreenAnalyticsEvent.RemoveTokenClicked(currencySymbol = currencySymbol), - ) - - removeSelectedFromToken() - } - - private fun removeSelectedFromToken() { - fromCurrencyStatus.value = null - - controller.update(transformer = RemoveSelectedFromTokenTransformer) - } - - private fun removeSelectedToToken() { - _toCurrencyStatus.value = null - - controller.update( - transformer = RemoveSelectedToTokenTransformer(onRemoveFromTokenClick = ::removeSelectedFromToken), - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt deleted file mode 100644 index da564d9d1b..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/ExchangeCard.kt +++ /dev/null @@ -1,214 +0,0 @@ -package com.tangem.features.onramp.swap.ui - -import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import com.tangem.common.ui.account.AccountLabel -import com.tangem.core.ui.components.account.AccountIconSize -import com.tangem.core.ui.components.currency.icon.CurrencyIconState -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.components.rows.NetworkTitle -import com.tangem.core.ui.components.token.TokenItem -import com.tangem.core.ui.components.token.state.TokenItemState -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.SwapSelectTokenScreenTestTags -import com.tangem.core.ui.utils.dashedBorder -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.entity.ExchangeCardUM - -/** - * Exchange card - * - * @param state state - * @param isBalanceHidden is balance hidden - * @param modifier modifier - * -[REDACTED_AUTHOR] - */ -@Composable -internal fun ExchangeCard(state: ExchangeCardUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxWidth() - .heightIn(min = 116.dp) - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.primary) - .testTag(SwapSelectTokenScreenTestTags.YOU_SWAP_BLOCK), - verticalArrangement = Arrangement.SpaceBetween, - ) { - Title( - titleUM = state.titleUM, - removeButtonUM = state.removeButtonUM, - ) - - AnimatedContent( - targetState = state, - transitionSpec = { - fadeIn(animationSpec = tween(durationMillis = 220, delayMillis = 90)) - .togetherWith(fadeOut(animationSpec = tween(durationMillis = 90))) - }, - label = "TokenItem's changing", - ) { animatedState -> - when (animatedState) { - is ExchangeCardUM.Empty -> EmptyTokenBlock(text = animatedState.subtitleReference) - is ExchangeCardUM.Filled -> { - TokenItem(state = animatedState.tokenItemState, isBalanceHidden = isBalanceHidden) - } - } - } - } -} - -@Composable -private fun Title(titleUM: ExchangeCardUM.TitleUM, removeButtonUM: ExchangeCardUM.RemoveButtonUM?) { - NetworkTitle( - title = { - AnimatedContent( - titleUM, - ) { currentState -> - when (currentState) { - is ExchangeCardUM.TitleUM.Account -> Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = currentState.prefixText.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - AccountLabel( - name = currentState.name, - icon = currentState.icon, - iconSize = AccountIconSize.ExtraSmall, - nameStyle = TangemTheme.typography.subtitle2, - nameColor = TangemTheme.colors.text.tertiary, - ) - } - is ExchangeCardUM.TitleUM.Text -> Text( - text = currentState.title.resolveReference(), - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.subtitle2, - ) - } - } - }, - action = { RemoveButton(state = removeButtonUM) }, - ) -} - -@Composable -private fun RemoveButton(state: ExchangeCardUM.RemoveButtonUM?) { - AnimatedVisibility(visible = state != null) { - state ?: return@AnimatedVisibility - - Text( - text = stringResourceSafe(id = R.string.manage_tokens_remove), - modifier = Modifier.clickable( - indication = ripple(bounded = false), - interactionSource = remember { MutableInteractionSource() }, - onClick = state.onClick, - ), - color = TangemTheme.colors.text.accent, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.body2, - ) - } -} - -@Composable -private fun EmptyTokenBlock(text: TextReference, modifier: Modifier = Modifier) { - Box( - modifier = modifier - .padding(horizontal = 12.dp, vertical = 13.dp) - .heightIn(min = 50.dp) - .fillMaxWidth() - .dashedBorder( - color = TangemTheme.colors.icon.informative, - shape = RoundedCornerShape(16.dp), - dashLength = 2.dp, - gapLength = 6.dp, - ) - .padding(vertical = 15.dp), - contentAlignment = Alignment.Center, - ) { - Text( - text = text.resolveReference(), - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = TangemTheme.typography.body2, - modifier = Modifier.testTag(SwapSelectTokenScreenTestTags.CHOOSE_TOKEN_TEXT), - ) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview_ExchangeCard(@PreviewParameter(ExchangeCardUMProvider::class) state: ExchangeCardUM) { - TangemThemePreview { - ExchangeCard( - state = state, - isBalanceHidden = false, - modifier = Modifier - .background(TangemTheme.colors.background.secondary) - .padding(16.dp), - ) - } -} - -private class ExchangeCardUMProvider : PreviewParameterProvider { - - override val values: Sequence = sequenceOf( - ExchangeCardUM.Empty( - titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)), - subtitleReference = resourceReference(id = R.string.action_buttons_you_want_to_swap), - ), - createFilled(removeButtonUM = null), - createFilled(removeButtonUM = ExchangeCardUM.RemoveButtonUM { }), - ) - - private fun createFilled(removeButtonUM: ExchangeCardUM.RemoveButtonUM?): ExchangeCardUM.Filled { - return ExchangeCardUM.Filled( - titleUM = ExchangeCardUM.TitleUM.Text(resourceReference(id = R.string.swapping_from_title)), - removeButtonUM = removeButtonUM, - tokenItemState = TokenItemState.Content( - id = "1", - iconState = CurrencyIconState.Locked, - titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Bitcoin")), - fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), - subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), - subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( - price = "34 496,75 \$", - priceChangePercent = "0,43 %", - type = PriceChangeType.DOWN, - ), - onItemClick = {}, - onItemLongClick = {}, - ), - ) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt deleted file mode 100644 index 5d3e457cc8..0000000000 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/ui/SwapSelectTokens.kt +++ /dev/null @@ -1,214 +0,0 @@ -package com.tangem.features.onramp.swap.ui - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.unit.dp -import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.components.appbar.AppBarWithBackButton -import com.tangem.core.ui.components.list.InfiniteListHandler -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection -import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.availablepairs.AvailableSwapPairsComponent -import com.tangem.features.onramp.swap.availablepairs.market.state.SwapMarketState -import com.tangem.features.onramp.swap.entity.ExchangeCardUM -import com.tangem.features.onramp.swap.entity.SwapSelectTokensUM -import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent -import com.tangem.features.onramp.tokenlist.entity.TokenListUM - -private const val LOAD_MORE_BUFFER = 25 - -/** - * Swap select tokens - * - * @param state state - * @param selectFromTokenListComponent select "from" token list component - * @param selectToTokenListComponent select "to" token list component - * @param modifier modifier - * -[REDACTED_AUTHOR] - */ -@OptIn(ExperimentalFoundationApi::class) -@Composable -internal fun SwapSelectTokens( - state: SwapSelectTokensUM, - selectFromTokenListComponent: OnrampTokenListComponent, - selectFromTokenListState: TokenListUM, - selectToTokenListComponent: AvailableSwapPairsComponent, - selectToTokenListState: TokenListUM, - modifier: Modifier = Modifier, -) { - BackHandler(onBack = state.onBackClick) - - val nestedScrollConnection = rememberHideKeyboardNestedScrollConnection() - val lazyListState = rememberLazyListState() - - LazyColumn( - modifier = modifier - .nestedScroll(nestedScrollConnection) - .background(TangemTheme.colors.background.secondary) - .imePadding() - .systemBarsPadding(), - state = lazyListState, - contentPadding = PaddingValues(bottom = 8.dp), - ) { - swapSelectTokensContent( - state = state, - selectFromTokenListComponent = selectFromTokenListComponent, - selectFromTokenListState = selectFromTokenListState, - selectToTokenListComponent = selectToTokenListComponent, - selectToTokenListState = selectToTokenListState, - ) - } - - ScrollToTopEffect(state = state, lazyListState = lazyListState) - - MarketsHandlers( - state = state, - selectToTokenListState = selectToTokenListState, - lazyListState = lazyListState, - ) -} - -@OptIn(ExperimentalFoundationApi::class) -private fun LazyListScope.swapSelectTokensContent( - state: SwapSelectTokensUM, - selectFromTokenListComponent: OnrampTokenListComponent, - selectFromTokenListState: TokenListUM, - selectToTokenListComponent: AvailableSwapPairsComponent, - selectToTokenListState: TokenListUM, -) { - stickyHeader(key = "header") { - AppBarWithBackButton( - onBackClick = state.onBackClick, - text = stringResourceSafe(id = R.string.common_swap), - iconRes = R.drawable.ic_close_24, - containerColor = TangemTheme.colors.background.secondary, - ) - } - - item(key = "exchange_from", contentType = "exchange_from") { - ExchangeCard( - state = state.exchangeFrom, - isBalanceHidden = state.isBalanceHidden, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(top = 8.dp, bottom = 12.dp) - .animateItem(), - ) - } - - if (state.exchangeFrom is ExchangeCardUM.Empty) { - with(selectFromTokenListComponent) { - content(uiState = selectFromTokenListState, modifier = Modifier) - } - } - - if (state.exchangeFrom is ExchangeCardUM.Filled) { - exchangeToSection( - state = state, - selectToTokenListComponent = selectToTokenListComponent, - selectToTokenListState = selectToTokenListState, - ) - } -} - -@OptIn(ExperimentalFoundationApi::class) -private fun LazyListScope.exchangeToSection( - state: SwapSelectTokensUM, - selectToTokenListComponent: AvailableSwapPairsComponent, - selectToTokenListState: TokenListUM, -) { - item(key = "exchange_to", contentType = "exchange_to") { - if (selectToTokenListState.warning != NotificationUM.Warning.SwapNoAvailablePair) { - ExchangeCard( - state = state.exchangeTo, - isBalanceHidden = state.isBalanceHidden, - modifier = Modifier - .padding(horizontal = 16.dp) - .padding(bottom = 12.dp) - .animateItem(), - ) - } - } - - if (state.exchangeTo is ExchangeCardUM.Empty) { - with(selectToTokenListComponent) { - content(uiState = selectToTokenListState, modifier = Modifier.padding(horizontal = 16.dp)) - } - } -} - -@Composable -private fun ScrollToTopEffect(state: SwapSelectTokensUM, lazyListState: LazyListState) { - LaunchedEffect(state.exchangeFrom !is ExchangeCardUM.Empty) { - lazyListState.scrollToItem(index = 0) - } -} - -@Composable -private fun MarketsHandlers( - state: SwapSelectTokensUM, - selectToTokenListState: TokenListUM, - lazyListState: LazyListState, -) { - // Markets for "to" token list - if (state.exchangeFrom is ExchangeCardUM.Filled && state.exchangeTo is ExchangeCardUM.Empty) { - MarketsPaginationHandler( - marketsState = selectToTokenListState.marketsState, - lazyListState = lazyListState, - ) - } -} - -@Composable -private fun MarketsPaginationHandler(marketsState: SwapMarketState?, lazyListState: LazyListState) { - (marketsState as? SwapMarketState.Content)?.let { content -> - VisibleItemsTracker(lazyListState = lazyListState, marketState = content) - - InfiniteListHandler( - listState = lazyListState, - buffer = LOAD_MORE_BUFFER, - triggerLoadMoreCheckOnItemsCountChange = true, - onLoadMore = remember(content) { - { - content.loadMore() - true - } - }, - ) - } -} - -@Composable -private fun VisibleItemsTracker(lazyListState: LazyListState, marketState: SwapMarketState.Content) { - val visibleItems by remember { - derivedStateOf { - lazyListState.layoutInfo.visibleItemsInfo - .mapNotNull { itemInfo -> - marketState.items.find { it.getComposeKey() == itemInfo.key }?.id - } - } - } - - LaunchedEffect(visibleItems) { - marketState.visibleIdsChanged(visibleItems) - } -} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/AccountAvailabilityTokenUM.kt similarity index 87% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/AccountAvailabilityTokenUM.kt index f0856222ba..daae2aff89 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/AccountAvailabilityTokenUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/AccountAvailabilityTokenUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.swap.entity +package com.tangem.features.onramp.tokenlist.entity import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrencyStatus diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt index 04f7008ff8..32992afc16 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/TokenListUM.kt @@ -3,7 +3,6 @@ package com.tangem.features.onramp.tokenlist.entity import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM -import com.tangem.features.onramp.swap.availablepairs.market.state.SwapMarketState import kotlinx.collections.immutable.ImmutableList /** @@ -13,7 +12,6 @@ import kotlinx.collections.immutable.ImmutableList * @property availableItems available items (search bar, header, tokens) * @property unavailableItems unavailable items (header, tokens) * @property isBalanceHidden flag that indicates if balance should be hidden - * @property marketsState markets list state (null when markets should not be shown) * [REDACTED_AUTHOR] */ @@ -23,8 +21,7 @@ internal data class TokenListUM( val unavailableItems: ImmutableList, val tokensListData: TokenListUMData, val isBalanceHidden: Boolean, - val warning: NotificationUM? = null, - val marketsState: SwapMarketState? = null, + val warning: NotificationUM? = null ) internal sealed interface TokenListUMData { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingAccountTokenItemConverter.kt similarity index 94% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingAccountTokenItemConverter.kt index 61552e4822..33c0f75113 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingAccountTokenItemConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingAccountTokenItemConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.swap.availablepairs.entity.converters +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter import com.tangem.common.ui.account.TokensListPortfolioItemConverter diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingTokenListItemConverter.kt similarity index 93% rename from features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt rename to features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingTokenListItemConverter.kt index 68932d0f66..73d4dcde43 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/entity/converters/LoadingTokenListItemConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/LoadingTokenListItemConverter.kt @@ -1,4 +1,4 @@ -package com.tangem.features.onramp.swap.availablepairs.entity.converters +package com.tangem.features.onramp.tokenlist.entity.transformer import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt index 797dfec9df..70751009af 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt @@ -4,8 +4,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingAccountTokenItemConverter -import com.tangem.features.onramp.swap.availablepairs.entity.converters.LoadingTokenListItemConverter import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt index 791645af20..39afe4e4e1 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenItemConverter.kt @@ -8,7 +8,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.tokenlist.entity.AccountAvailabilityUM import com.tangem.features.onramp.tokenlist.entity.utils.OnrampTokenItemStateConverterFactory import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt index 8ccc9dabe0..81a9163b2e 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/UpdateAccountTokenListTransformer.kt @@ -8,7 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM +import com.tangem.features.onramp.tokenlist.entity.AccountAvailabilityUM import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import com.tangem.features.onramp.tokenlist.entity.TokenListUMTransformer diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index b11f85dce7..500c36d02c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -26,8 +26,8 @@ import com.tangem.domain.tokens.GetAssetRequirementsUseCase import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R -import com.tangem.features.onramp.swap.entity.AccountAvailabilityUM -import com.tangem.features.onramp.swap.entity.AccountCurrencyUM +import com.tangem.features.onramp.tokenlist.entity.AccountAvailabilityUM +import com.tangem.features.onramp.tokenlist.entity.AccountCurrencyUM import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.* import com.tangem.features.onramp.tokenlist.entity.transformer.SetLoadingAccountTokenListTransformer diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt index e878df7dbd..1654052d6a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/ui/OnrampTokenList.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.R -import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.fields.TangemSearchBarDefaults import com.tangem.core.ui.components.fields.entity.SearchBarUM @@ -37,26 +36,11 @@ 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.features.onramp.swap.availablepairs.ui.swapMarketsListItems import com.tangem.features.onramp.tokenlist.entity.TokenListUM import com.tangem.features.onramp.tokenlist.entity.TokenListUMData import com.tangem.features.onramp.tokenlist.ui.preview.PreviewTokenListUMProvider import kotlinx.collections.immutable.ImmutableList -/** - * Token list for swap - automatically switches between normal and search mode with markets - * - * @param state state - * - */ -internal fun LazyListScope.onrampSwapTokenList(state: TokenListUM) { - if (state.marketsState != null) { - onrampTokenListWithMarkets(state = state) - } else { - onrampTokenList(state = state) - } -} - /** * Token list - normal mode (without markets) * @@ -74,39 +58,6 @@ internal fun LazyListScope.onrampTokenList(state: TokenListUM) { tokensListData(state = state) } -/** - * Token list with markets - search mode - * - * @param state state - */ -private fun LazyListScope.onrampTokenListWithMarkets(state: TokenListUM) { - val itemModifier = Modifier.padding(horizontal = 16.dp) - - warningOrSearchBar(state = state, itemModifier = itemModifier) - - // Check if user has any assets to show - val hasAssets = state.availableItems.isNotEmpty() || - state.unavailableItems.isNotEmpty() || - state.tokensListData.totalTokensCount != 0 - - if (hasAssets) { - assetsTitle( - count = state.tokensListData.totalTokensCount, - showCount = state.marketsState?.shouldAssetsCount == true, - ) - - tokensList(items = state.availableItems, isBalanceHidden = state.isBalanceHidden) - - tokensList(items = state.unavailableItems, isBalanceHidden = state.isBalanceHidden) - - tokensListData(state = state) - - item { SpacerH32() } - } - - state.marketsState?.let(::swapMarketsListItems) -} - private fun LazyListScope.warningOrSearchBar(state: TokenListUM, itemModifier: Modifier) { if (state.warning == null) { searchBarItem(searchBarUM = state.searchBarUM, modifier = itemModifier) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index 4d35b9c0fe..dcdd04d56a 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -49,11 +49,9 @@ import com.tangem.datasource.api.express.models.request.LeastTokenInfo as Networ internal class DefaultSwapRepository( private val tangemExpressApi: TangemExpressApi, private val coroutineDispatcher: CoroutineDispatcherProvider, - private val walletManagersFacade: WalletManagersFacade, private val errorsDataConverter: ErrorsDataConverter, private val dataSignatureVerifier: DataSignatureVerifier, private val appPreferencesStore: AppPreferencesStore, - private val rampStateManager: RampStateManager, private val expressHistoryDao: ExpressHistoryDao, moshi: Moshi, ) : SwapRepository { @@ -122,98 +120,6 @@ internal class DefaultSwapRepository( } } - override suspend fun getPairsOnly( - userWallet: UserWallet, - initialCurrency: LeastTokenInfo, - currencyList: List, - isIgnoreExpress: Boolean, - ): PairsWithProviders { - return withContext(coroutineDispatcher.io) { - val currenciesList = filterByAssetRequirements(userWallet, currencyList) - - if (isIgnoreExpress) { - buildLocalPairs(initialCurrency, currenciesList) - } else { - fetchExpressPairs(userWallet, initialCurrency, currenciesList) - } - } - } - - private fun buildLocalPairs( - initialCurrency: LeastTokenInfo, - currenciesList: List, - ): PairsWithProviders { - val pairs = currenciesList.map { tokenInfo -> - SwapPairLeast( - from = initialCurrency, - to = LeastTokenInfo( - contractAddress = tokenInfo.contractAddress, - network = tokenInfo.network, - ), - providers = emptyList(), - ) - } - return PairsWithProviders(pairs = pairs, allProviders = emptyList()) - } - - private suspend fun fetchExpressPairs( - userWallet: UserWallet, - initialCurrency: LeastTokenInfo, - currenciesList: List, - ): PairsWithProviders { - try { - val initial = NetworkLeastTokenInfo( - contractAddress = initialCurrency.contractAddress, - network = initialCurrency.network, - ) - - val allPairs = supervisorScope { - val pairsDeferred = async { - getPairsInternal( - userWallet = userWallet, - from = arrayListOf(initial), - to = currenciesList, - ) - } - - val reversedPairsDeferred = async { - getPairsInternal( - userWallet = userWallet, - from = currenciesList, - to = arrayListOf(initial), - ) - } - - pairsDeferred.await().getOrThrow() + reversedPairsDeferred.await().getOrThrow() - } - - return swapPairInfoConverter.convert( - SwapPairsWithProviders( - swapPair = allPairs, - providers = emptyList(), - ), - ) - } catch (exception: Exception) { - if (exception is ApiResponseError.HttpException) { - throw ExpressException(errorsDataConverter.convert(exception.errorBody.orEmpty())) - } else { - throw exception - } - } - } - - private suspend fun filterByAssetRequirements( - userWallet: UserWallet, - currencyList: List, - ): List { - return currencyList - .filter { currency -> - val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, currency) - rampStateManager.checkAssetRequirements(requirements) - } - .map { currency -> leastTokenInfoConverter.convert(currency) } - } - private suspend fun getPairsInternal( userWallet: UserWallet, from: List, diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt index ca24786ea1..4a05eab85d 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/di/SwapDataModule.kt @@ -39,22 +39,18 @@ internal class SwapDataModule { tangemExpressApi: TangemExpressApi, coroutineDispatcher: CoroutineDispatcherProvider, dataSignature: DataSignatureVerifier, - walletManagerFacade: WalletManagersFacade, errorsDataConverter: ErrorsDataConverter, @NetworkMoshi moshi: Moshi, appPreferencesStore: AppPreferencesStore, - rampStateManager: RampStateManager, expressHistoryDao: ExpressHistoryDao, ): SwapRepository { return DefaultSwapRepository( tangemExpressApi = tangemExpressApi, coroutineDispatcher = coroutineDispatcher, - walletManagersFacade = walletManagerFacade, errorsDataConverter = errorsDataConverter, dataSignatureVerifier = dataSignature, moshi = moshi, appPreferencesStore = appPreferencesStore, - rampStateManager = rampStateManager, expressHistoryDao = expressHistoryDao, ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt deleted file mode 100644 index c0bfc9041f..0000000000 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/GetAvailablePairsUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.feature.swap.domain - -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.feature.swap.domain.api.SwapRepository -import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo -import com.tangem.feature.swap.domain.models.domain.SwapPairLeast - -class GetAvailablePairsUseCase( - private val swapRepository: SwapRepository, -) { - - suspend operator fun invoke( - userWallet: UserWallet, - initialCurrency: LeastTokenInfo, - currencies: List, - ): List { - return swapRepository.getPairsOnly( - userWallet = userWallet, - initialCurrency = initialCurrency, - currencyList = currencies, - isIgnoreExpress = true, - ).pairs - } -} \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt index fb7b0b78ab..6e68ce66ad 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/api/SwapRepository.kt @@ -16,14 +16,6 @@ interface SwapRepository { currencyList: List, ): PairsWithProviders - /** Express getPairs request variant without providers request */ - suspend fun getPairsOnly( - userWallet: UserWallet, - initialCurrency: LeastTokenInfo, - currencyList: List, - isIgnoreExpress: Boolean = false, - ): PairsWithProviders - suspend fun getExchangeStatus( userWallet: UserWallet?, userWalletId: UserWalletId,