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 6817e44fe9..0751f831e7 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 @@ -24,7 +24,6 @@ import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensMode import com.tangem.features.managetokens.component.ManageTokensSource -import com.tangem.features.markets.tokenlist.MarketsTokenListComponent import com.tangem.features.nft.component.NFTComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* @@ -64,7 +63,6 @@ internal class ChildFactory @Inject constructor( private val walletHardwareBackupComponentFactory: WalletHardwareBackupComponent.Factory, private val disclaimerComponentFactory: DisclaimerComponent.Factory, private val manageTokensComponentFactory: ManageTokensComponent.Factory, - private val marketsTokenListComponentFactory: MarketsTokenListComponent.FactoryScreen, private val onrampComponentFactory: OnrampComponent.Factory, private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, @@ -459,8 +457,8 @@ internal class ChildFactory @Inject constructor( is AppRoute.Markets -> { createComponentChild( context = context, - params = Unit, - componentFactory = marketsTokenListComponentFactory, + params = FeedEntryRoute.MarketTokenList, + componentFactory = feedEntryComponentFactory, ) } is AppRoute.Usedesk -> { // TODO [REDACTED_TASK_KEY] pass params diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt index 4bc37fc48e..bf888af247 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt @@ -6,9 +6,9 @@ import com.tangem.common.routing.DeepLinkRoute import com.tangem.common.routing.DeepLinkScheme import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler import com.tangem.features.onramp.deeplink.SellDeepLinkHandler diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt index 4d40266dc8..1692f38f75 100644 --- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -4,9 +4,9 @@ import android.net.Uri import com.tangem.common.routing.AppRoute import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler import com.tangem.features.onramp.deeplink.SellDeepLinkHandler diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsDeepLinkHandler.kt similarity index 70% rename from features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt rename to features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsDeepLinkHandler.kt index e88a78c729..5909a4876a 100644 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsDeepLinkHandler.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.deeplink +package com.tangem.features.feed.entry.deeplink interface MarketsDeepLinkHandler { diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsTokenDetailDeepLinkHandler.kt similarity index 83% rename from features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt rename to features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsTokenDetailDeepLinkHandler.kt index 45ff62203c..861814989a 100644 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/MarketsTokenDetailDeepLinkHandler.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.deeplink +package com.tangem.features.feed.entry.deeplink import kotlinx.coroutines.CoroutineScope diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsDeepLinkHandler.kt similarity index 81% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsDeepLinkHandler.kt index e620aaae47..2e81775f75 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsDeepLinkHandler.kt @@ -1,7 +1,8 @@ -package com.tangem.features.markets.deeplink +package com.tangem.features.feed.deeplink import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt similarity index 96% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt index b1f7050b8b..3ef3be6ce2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt @@ -1,4 +1,4 @@ -package com.tangem.features.markets.deeplink +package com.tangem.features.feed.deeplink import arrow.core.getOrElse import com.tangem.common.routing.AppRoute @@ -10,6 +10,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.GetTokenMarketInfoUseCase import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/MarketsDeepLinkModule.kt similarity index 62% rename from features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/MarketsDeepLinkModule.kt index 168ea58a46..def0bf84fb 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/MarketsDeepLinkModule.kt @@ -1,9 +1,9 @@ -package com.tangem.features.markets.deeplink.di +package com.tangem.features.feed.deeplink.di -import com.tangem.features.markets.deeplink.DefaultMarketsDeepLinkHandler -import com.tangem.features.markets.deeplink.DefaultMarketsTokenDetailDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler -import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler +import com.tangem.features.feed.deeplink.DefaultMarketsDeepLinkHandler +import com.tangem.features.feed.deeplink.DefaultMarketsTokenDetailDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler +import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt deleted file mode 100644 index 8062a9a1f6..0000000000 --- a/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.features.markets.tokenlist - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.TokenMarketParams - -@Stable -interface MarketsTokenListComponent : ComposableContentComponent { - - @Composable - fun BottomSheetContent( - bottomSheetState: State, - onHeaderSizeChange: (Dp) -> Unit, - modifier: Modifier, - ) - - interface FactoryScreen : ComponentFactory - - interface FactoryBottomSheet { - fun create( - context: AppComponentContext, - params: Unit, - onTokenClick: ((TokenMarketParams, AppCurrency) -> Unit)?, - ): MarketsTokenListComponent - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt deleted file mode 100644 index 16c594bee1..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl - -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.lifecycle.compose.LifecycleStartEffect -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.AppRoute.MarketsTokenDetails.AnalyticsParams -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.WindowInsetsZero -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.TokenMarketParams -import com.tangem.domain.markets.toSerializableParam -import com.tangem.features.markets.tokenlist.MarketsTokenListComponent -import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel -import com.tangem.features.markets.tokenlist.impl.ui.MarketsList -import com.tangem.features.markets.tokenlist.impl.ui.MarketsListWithBack -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -@Suppress("UnusedPrivateMember") -internal class DefaultMarketsTokenListComponent @AssistedInject constructor( - @Assisted appComponentContext: AppComponentContext, - @Assisted params: Unit, - @Assisted onTokenClick: ((TokenMarketParams, AppCurrency) -> Unit)? = null, -) : AppComponentContext by appComponentContext, MarketsTokenListComponent { - - private val model: MarketsListModel = getOrCreateModel() - - init { - model.tokenSelected - .onEach { (token, appCurrency) -> - // If specific routing call back is provided, invoke it (for example inner routing in Bottom Sheet) - // Otherwise navigate using app routing - if (onTokenClick != null) { - onTokenClick(token.toSerializableParam(), appCurrency) - } else { - router.push( - AppRoute.MarketsTokenDetails( - token = token.toSerializableParam(), - appCurrency = appCurrency, - shouldShowPortfolio = true, - analyticsParams = AnalyticsParams( - blockchain = null, - source = "Market", - ), - ), - ) - } - } - .launchIn(componentScope) - } - - @Composable - override fun BottomSheetContent( - bottomSheetState: State, - onHeaderSizeChange: (Dp) -> Unit, - modifier: Modifier, - ) { - LifecycleStartEffect(Unit) { - model.isVisibleOnScreen.value = true - onStopOrDispose { - model.isVisibleOnScreen.value = false - } - } - - val state by model.state.collectAsStateWithLifecycle() - val bsState by bottomSheetState - - LaunchedEffect(bsState) { - model.containerBottomSheetState.value = bsState - } - - MarketsList( - modifier = modifier, - state = state, - onHeaderSizeChange = onHeaderSizeChange, - bottomSheetState = bsState, - ) - } - - @Composable - override fun Content(modifier: Modifier) { - LifecycleStartEffect(Unit) { - model.isVisibleOnScreen.value = true - onStopOrDispose { - model.isVisibleOnScreen.value = false - } - } - - val state by model.state.collectAsStateWithLifecycle() - - Scaffold( - contentWindowInsets = WindowInsetsZero, - containerColor = TangemTheme.colors.background.primary, - ) { paddingValues -> - MarketsListWithBack( - modifier = Modifier - .statusBarsPadding() - .imePadding() - .padding(paddingValues), - state = state, - bottomSheetState = BottomSheetState.EXPANDED, - onBackClick = router::pop, - ) - } - } - - @AssistedFactory - interface FactoryBottomSheet : MarketsTokenListComponent.FactoryBottomSheet { - override fun create( - context: AppComponentContext, - params: Unit, - onTokenClick: ((TokenMarketParams, AppCurrency) -> Unit)?, - ): DefaultMarketsTokenListComponent - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt deleted file mode 100644 index ee943d293b..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/analytics/MarketsListAnalyticsEvent.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.analytics - -import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM - -internal sealed class MarketsListAnalyticsEvent( - event: String, - params: Map = emptyMap(), -) : AnalyticsEvent(category = "Markets", event = event, params = params) { - - class BottomSheetOpened : MarketsListAnalyticsEvent(event = "Markets Screen Opened") - - data class SortBy( - val sortByTypeUM: SortByTypeUM, - val interval: MarketsListUM.TrendInterval, - ) : MarketsListAnalyticsEvent( - event = "Sort By", - params = mapOf( - "Type" to when (sortByTypeUM) { - SortByTypeUM.Rating -> "Rating" - SortByTypeUM.Trending -> "Trending" - SortByTypeUM.ExperiencedBuyers -> "Buyers" - SortByTypeUM.TopGainers -> "Gainers" - SortByTypeUM.TopLosers -> "Losers" - SortByTypeUM.Staking -> "Staking" - SortByTypeUM.YieldSupply -> "Yield Supply" - }, - "Period" to when (interval) { - MarketsListUM.TrendInterval.H24 -> "24h" - MarketsListUM.TrendInterval.D7 -> "7d" - MarketsListUM.TrendInterval.M1 -> "1m" - }, - ), - ) - class YieldModePromoShown : MarketsListAnalyticsEvent(event = "Notice - Yield Mode Promo") - - class YieldModePromoClosed : MarketsListAnalyticsEvent(event = "Yield Mode Promo Closed") - - class YieldModeMoreInfoClicked : MarketsListAnalyticsEvent(event = "Yield Mode More Info") - - data class TokenSearched(val wasTokenFound: Boolean) : MarketsListAnalyticsEvent( - event = "Token Searched", - params = mapOf( - "Result" to if (wasTokenFound) "Yes" else "No", - ), - ) - - class ShowTokens : MarketsListAnalyticsEvent(event = "Button - Show Tokens") -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt deleted file mode 100644 index 2c1ad7bb23..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.di - -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.features.markets.tokenlist.MarketsTokenListComponent -import com.tangem.features.markets.tokenlist.impl.DefaultMarketsTokenListComponent -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface ComponentModule { - - @Binds - @Singleton - fun bindMarketsTokenListBottomSheetComponent( - factory: DefaultMarketsTokenListComponent.FactoryBottomSheet, - ): MarketsTokenListComponent.FactoryBottomSheet -} - -@Module -@InstallIn(SingletonComponent::class) -internal class ComponentProvideModule { - - @Provides - @Singleton - fun providesMarketsTokenListScreenComponent( - factory: DefaultMarketsTokenListComponent.FactoryBottomSheet, - ): MarketsTokenListComponent.FactoryScreen { - return object : MarketsTokenListComponent.FactoryScreen { - override fun create(context: AppComponentContext, params: Unit): MarketsTokenListComponent { - return factory.create(context, params, null) - } - } - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt deleted file mode 100644 index fca279c2f6..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ModelModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.di - -import com.tangem.core.decompose.di.ModelComponent -import com.tangem.core.decompose.model.Model -import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap - -@Module -@InstallIn(ModelComponent::class) -internal interface ModelModule { - - @Binds - @IntoMap - @ClassKey(MarketsListModel::class) - fun provideMarketsListModel(model: MarketsListModel): Model -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt deleted file mode 100644 index c898c68a73..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsListModel.kt +++ /dev/null @@ -1,344 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model - -import androidx.compose.runtime.Stable -import arrow.core.Either -import arrow.core.getOrElse -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase -import com.tangem.domain.markets.ShouldShowYieldModeMarketPromoUseCase -import com.tangem.domain.markets.TokenMarket -import com.tangem.domain.markets.TokenMarketListConfig -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.promo.PromoRepository -import com.tangem.domain.settings.usercountry.GetUserCountryUseCase -import com.tangem.domain.settings.usercountry.models.UserCountry -import com.tangem.domain.settings.usercountry.models.UserCountryError -import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions -import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent -import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager -import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager -import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM -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.coroutines.* -import kotlinx.coroutines.flow.* -import javax.inject.Inject - -private const val UPDATE_QUOTES_TIMER_MILLIS = 60000L -private const val SEARCH_QUERY_DEBOUNCE_MILLIS = 800L - -@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) -@ModelScoped -@Stable -@Suppress("LongParameterList") -internal class MarketsListModel @Inject constructor( - override val dispatchers: CoroutineDispatcherProvider, - getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, - getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - shouldShowYieldModeMarketPromoUseCase: ShouldShowYieldModeMarketPromoUseCase, - private val promoRepository: PromoRepository, - private val getUserCountryUseCase: GetUserCountryUseCase, - private val analyticsEventHandler: AnalyticsEventHandler, -) : Model() { - - private val updateQuotesJob = JobHolder() - - private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency -> - maybeAppCurrency.getOrElse { AppCurrency.Default } - }.stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = AppCurrency.Default, - ) - - private val visibleItemIds = MutableStateFlow>(emptyList()) - - private lateinit var activeListManager: MarketsListBatchFlowManager - - private val marketsListUMStateManager = MarketsListUMStateManager( - currentVisibleIds = Provider { visibleItemIds.value }, - onLoadMoreUiItems = { activeListManager.loadMore() }, - visibleItemsChanged = { visibleItemIds.value = it }, - onRetryButtonClicked = { activeListManager.reload() }, - onTokenClick = { onTokenUIClicked(it) }, - onShowTokensUnder100kClicked = { analyticsEventHandler.send(MarketsListAnalyticsEvent.ShowTokens()) }, - ) - - private val mainMarketsListManager = MarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase, - batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main, - currentAppCurrency = Provider { currentAppCurrency.value }, - currentTrendInterval = Provider { marketsListUMStateManager.selectedInterval }, - currentSortByType = Provider { marketsListUMStateManager.selectedSortByType }, - currentSearchText = Provider { null }, - modelScope = modelScope, - dispatchers = dispatchers, - ) - - private val searchMarketsListManager = MarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase = getMarketsTokenListFlowUseCase, - batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Search, - currentAppCurrency = Provider { currentAppCurrency.value }, - currentTrendInterval = Provider { marketsListUMStateManager.selectedInterval }, - currentSortByType = Provider { SortByTypeUM.Rating }, - currentSearchText = Provider { marketsListUMStateManager.searchQuery }, - modelScope = modelScope, - dispatchers = dispatchers, - ) - - init { - activeListManager = mainMarketsListManager - } - - private val _tokenSelected = MutableSharedFlow>() - - val tokenSelected = _tokenSelected.asSharedFlow() - - val containerBottomSheetState = MutableStateFlow(BottomSheetState.COLLAPSED) - val isVisibleOnScreen = MutableStateFlow(false) - - val state = marketsListUMStateManager.state.asStateFlow() - - init { - @Suppress("UnnecessaryParentheses") modelScope.launch { - marketsListUMStateManager.isInSearchStateFlow.flatMapLatest { isInSearchMode -> - if (isInSearchMode) { - combine( - flow = searchMarketsListManager.uiItems, - flow2 = searchMarketsListManager.isInInitialLoadingErrorState, - flow3 = searchMarketsListManager.isSearchNotFoundState, - flow4 = shouldShowYieldModeMarketPromoUseCase( - appCurrency = currentAppCurrency.value, - interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ).conflate(), - flow5 = getUserCountryUseCase.invoke(), - ) { uiItems, isInInitialLoadingErrorState, isSearchNotFoundState, isYieldModePromo, userCountry -> - MarketsItemsData( - items = uiItems, - isInErrorState = isInInitialLoadingErrorState, - isSearchNotFound = isSearchNotFoundState, - shouldShowYieldModePromo = isYieldModePromo, - userCountry = userCountry, - ) - } - } else { - combine( - flow = mainMarketsListManager.uiItems, - flow2 = mainMarketsListManager.isInInitialLoadingErrorState, - flow3 = shouldShowYieldModeMarketPromoUseCase( - appCurrency = currentAppCurrency.value, - interval = marketsListUMStateManager.selectedInterval.toBatchRequestInterval(), - ).conflate(), - flow4 = getUserCountryUseCase.invoke(), - ) { uiItems, isInInitialLoadingErrorState, shouldShowYieldModePromo, userCountry -> - MarketsItemsData( - items = uiItems, - isInErrorState = isInInitialLoadingErrorState, - isSearchNotFound = false, - shouldShowYieldModePromo = shouldShowYieldModePromo, - userCountry = userCountry, - ) - } - } - }.collect { marketsItemsData -> - val isApplyFCARestrictions = marketsItemsData.userCountry.getOrNull().needApplyFCARestrictions() - val shouldShowYieldModePromo = marketsItemsData.shouldShowYieldModePromo && !isApplyFCARestrictions - if (marketsListUMStateManager.state.value.marketsNotificationUM == null && shouldShowYieldModePromo) { - analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoShown()) - } - - marketsListUMStateManager.onUiItemsChanged( - uiItems = marketsItemsData.items, - isInErrorState = marketsItemsData.isInErrorState, - isSearchNotFound = marketsItemsData.isSearchNotFound, - marketsNotificationUM = if (shouldShowYieldModePromo) { - MarketsNotificationUM.YieldSupplyPromo( - onClick = { - analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModeMoreInfoClicked()) - marketsListUMStateManager.selectedSortByType = SortByTypeUM.YieldSupply - }, - onCloseClick = { onYieldModeNotificationCloseClick() }, - ) - } else { - null - }, - ) - } - } - - state.onEach { currentState -> - if (currentState.list !is ListUM.Content) { - visibleItemIds.value = emptyList() - } - }.launchIn(modelScope) - - // update all lists when user's currency has changed - currentAppCurrency.drop(1).onEach { - mainMarketsListManager.reload() - if (marketsListUMStateManager.isInSearchState) { - searchMarketsListManager.reload() - } - }.launchIn(modelScope) - - // load charts when new batch is being loaded - mainMarketsListManager.onLastBatchLoadedSuccess.onEach { batchKey -> - mainMarketsListManager.loadCharts(setOf(batchKey), marketsListUMStateManager.selectedInterval) - modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS) - }.launchIn(modelScope) - - // listen currently selected interval, update charts if sorting=rating, or reload all list - modelScope.launch(dispatchers.default) { - marketsListUMStateManager.state.map { it.selectedInterval }.distinctUntilChanged().drop(1) - .collectLatest { interval -> - when (marketsListUMStateManager.selectedSortByType) { - SortByTypeUM.Rating -> { - mainMarketsListManager.updateUIWithSameState() - val batchKeys = mainMarketsListManager.getBatchKeysByItemIds(visibleItemIds.value) - mainMarketsListManager.loadCharts(batchKeys, interval) - } - else -> mainMarketsListManager.reload() - } - } - } - - // reload list when sorting type has changed - modelScope.launch { - marketsListUMStateManager.state - .map { state -> state.selectedSortBy } - .distinctUntilChanged() - .drop(1) - .collectLatest { - mainMarketsListManager.reload() - } - } - - // listen current visible batch and update charts - modelScope.launch { - visibleItemIds.mapNotNull { itemIds -> - if (itemIds.isNotEmpty()) { - activeListManager.getBatchKeysByItemIds(visibleItemIds.value) - } else { - null - } - }.distinctUntilChanged().collectLatest { visibleBatchKeys -> - // TODO load batch on scroll heat area - activeListManager.loadCharts(visibleBatchKeys, marketsListUMStateManager.selectedInterval) - } - } - - // ===Search=== - - modelScope.launch { - marketsListUMStateManager.isInSearchStateFlow.collectLatest { isInSearchMode -> - activeListManager = if (isInSearchMode) { - searchMarketsListManager - } else { - searchMarketsListManager.clearStateAndStopAllActions() - mainMarketsListManager - } - } - } - - modelScope.launch { - marketsListUMStateManager.searchQueryFlow.debounce(timeoutMillis = SEARCH_QUERY_DEBOUNCE_MILLIS) - .distinctUntilChanged().onEach { - if (it.isEmpty()) searchMarketsListManager.clearStateAndStopAllActions() - }.filter { it.isNotEmpty() && activeListManager == searchMarketsListManager }.collectLatest { - searchMarketsListManager.reload(searchText = it) - } - } - - modelScope.launch { - searchMarketsListManager.onLastBatchLoadedSuccess.collectLatest { batchKey -> - searchMarketsListManager.loadCharts(setOf(batchKey), marketsListUMStateManager.selectedInterval) - modelScope.loadQuotesWithTimer(timeMillis = UPDATE_QUOTES_TIMER_MILLIS) - } - } - - searchMarketsListManager.isSearchNotFoundState.onEach { isNotFound -> - if (isNotFound) { - analyticsEventHandler.send(MarketsListAnalyticsEvent.TokenSearched(wasTokenFound = false)) - } - }.launchIn(modelScope) - - searchMarketsListManager.onFirstBatchLoadedSuccess.onEach { - analyticsEventHandler.send(MarketsListAnalyticsEvent.TokenSearched(wasTokenFound = true)) - }.launchIn(modelScope) - - // analytics - initAnalytics() - - // initial loading - mainMarketsListManager.reload() - } - - fun TrendInterval.toBatchRequestInterval(): TokenMarketListConfig.Interval { - return when (this) { - TrendInterval.H24 -> TokenMarketListConfig.Interval.H24 - TrendInterval.D7 -> TokenMarketListConfig.Interval.WEEK - TrendInterval.M1 -> TokenMarketListConfig.Interval.MONTH - } - } - - private fun initAnalytics() { - containerBottomSheetState.onEach { bottomSheetState -> - if (bottomSheetState == BottomSheetState.EXPANDED) { - analyticsEventHandler.send(MarketsListAnalyticsEvent.BottomSheetOpened()) - } - }.launchIn(modelScope) - - state.filter { it.isInSearchMode.not() } - .map { MarketsListAnalyticsEvent.SortBy(it.selectedSortBy, it.selectedInterval) }.distinctUntilChanged() - .onEach { - analyticsEventHandler.send(it) - }.launchIn(modelScope) - } - - private fun onTokenUIClicked(token: MarketsListItemUM) { - modelScope.launch { - activeListManager.getTokenById(token.id)?.let { found -> - _tokenSelected.emit(found to currentAppCurrency.value) - } - } - } - - private fun CoroutineScope.loadQuotesWithTimer(timeMillis: Long) { - launch { - while (true) { - delay(timeMillis) - // Update quotes only when the container bottom sheet is in the expanded state - containerBottomSheetState.first { it == BottomSheetState.EXPANDED } - // and is visible on the screen - isVisibleOnScreen.first { it } - - activeListManager.updateQuotes() - } - }.saveIn(updateQuotesJob) - } - - private fun onYieldModeNotificationCloseClick() { - analyticsEventHandler.send(MarketsListAnalyticsEvent.YieldModePromoClosed()) - modelScope.launch { - promoRepository.setMarketsYieldSupplyNotificationHideClicked() - } - } - - private class MarketsItemsData( - val items: ImmutableList, - val isInErrorState: Boolean, - val isSearchNotFound: Boolean, - val shouldShowYieldModePromo: Boolean, - val userCountry: Either, - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt deleted file mode 100644 index 5870289d78..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/MarketsNotificationUM.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model - -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.features.markets.impl.R - -internal sealed class MarketsNotificationUM(val config: NotificationConfig) { - - data class YieldSupplyPromo( - val onClick: () -> Unit, - val onCloseClick: () -> Unit, - ) : MarketsNotificationUM( - config = NotificationConfig( - iconResId = R.drawable.img_yield_supply_in_market_notification, - title = resourceReference(R.string.markets_yield_supply_banner_title), - subtitle = TextReference.EMPTY, - onClick = onClick, - onCloseClick = onCloseClick, - ), - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt deleted file mode 100644 index 2e0926eaf5..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/converters/MarketsTokenItemConverter.kt +++ /dev/null @@ -1,159 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model.converters - -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.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.* -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.TokenMarket -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList -import java.math.BigDecimal -import java.math.RoundingMode - -internal class MarketsTokenItemConverter( - private val currentTrendInterval: TrendInterval, - 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, - ) - } - - 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, - changeType = changeType, - ) - } - - private fun TokenMarket.getChartData(): MarketChartRawData? { - val chart = when (currentTrendInterval) { - TrendInterval.H24 -> tokenCharts.h24 - TrendInterval.D7 -> tokenCharts.week - TrendInterval.M1 -> tokenCharts.month - } - - 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 = when (currentTrendInterval) { - TrendInterval.H24 -> tokenQuotesShort.h24ChangePercent - TrendInterval.D7 -> tokenQuotesShort.weekChangePercent - TrendInterval.M1 -> tokenQuotesShort.monthChangePercent - } - 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 = when (currentTrendInterval) { - TrendInterval.H24 -> tokenQuotesShort.h24ChangePercent - TrendInterval.D7 -> tokenQuotesShort.weekChangePercent - TrendInterval.M1 -> tokenQuotesShort.monthChangePercent - } - - return percent.format { percent() } - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt deleted file mode 100644 index e96284ced9..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListBatchFlowManager.kt +++ /dev/null @@ -1,367 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model.statemanager - -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.markets.* -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.tokenlist.impl.model.converters.MarketsTokenItemConverter -import com.tangem.features.markets.tokenlist.impl.model.utils.logAction -import com.tangem.features.markets.tokenlist.impl.model.utils.logStatus -import com.tangem.features.markets.tokenlist.impl.model.utils.logUpdateResults -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM.TrendInterval -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM -import com.tangem.pagination.Batch -import com.tangem.pagination.BatchAction -import com.tangem.pagination.BatchFetchResult -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.* - -private const val LOG_EVENTS = true - -@Suppress("LongParameterList") -internal class MarketsListBatchFlowManager( - getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase, - private val batchFlowType: GetMarketsTokenListFlowUseCase.BatchFlowType, - private val currentTrendInterval: Provider, - private val currentAppCurrency: Provider, - private val currentSearchText: Provider, - private val currentSortByType: 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 onLastBatchLoadedSuccess = batchFlow.state - .distinctUntilChanged { old, new -> old.status == new.status && old.data.size == new.data.size } - .mapNotNull { state -> - when (val status = state.status) { - is PaginationStatus.Paginating -> { - if (status.lastResult is BatchFetchResult.Success) { - state.data.lastOrNull()?.key - } else { - null - } - } - is PaginationStatus.EndOfPagination -> { - state.data.lastOrNull()?.key - } - else -> null - } - } - - val onFirstBatchLoadedSuccess = batchFlow.state - .distinctUntilChanged { old, new -> old.status == new.status && old.data.size == new.data.size } - .mapNotNull { state -> - when (val status = state.status) { - is PaginationStatus.Paginating -> { - if (status.lastResult is BatchFetchResult.Success) { - state.data.size == 1 - } else { - null - } - } - is PaginationStatus.EndOfPagination -> { - state.data.size == 1 - } - else -> null - } - } - .filter { isFirstBatch -> isFirstBatch } - - val isInInitialLoadingErrorState = batchFlow.state - .map { it.status is PaginationStatus.InitialLoadingError } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - - val isSearchNotFoundState = batchFlow.state - .map { state -> - currentSearchText().isNullOrEmpty().not() && - state.status is PaginationStatus.EndOfPagination && - state.data.isEmpty() - } - .distinctUntilChanged() - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - - 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) - - if (LOG_EVENTS) { - batchFlow.updateResults - .onEach { logUpdateResults(batchFlowType.name, it) } - .launchIn(modelScope) - - batchFlow.state - .map { it.status } - .onEach { logStatus(batchFlowType.name, it) } - .launchIn(modelScope) - - actionsFlow - .onEach { logAction(batchFlowType.name, it) } - .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 = MarketsTokenItemConverter(currentTrendInterval(), 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 (previousList == newBatch) return@mapIndexed batch - - Batch( - key = batch.key, - data = batch.data.mapIndexed { index, marketsListItemUM -> - val prevItem = prevBatch.data[index] - val newItem = newBatch.data[index] - - converter.update( - prevItem, - marketsListItemUM, - newItem, - ) - }, - ) - } - } - } - - 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 = currentTrendInterval().toBatchRequestInterval(), - order = currentSortByType().toRequestOrder(), - ), - ), - ) - } - } - - fun loadMore() { - modelScope.launch { - actionsFlow.emit(BatchAction.LoadMore()) - } - } - - fun updateUIWithSameState() { - modelScope.launch(dispatchers.default) { - val current = batchFlow.state.value.data - updateState(current, forceUpdate = true) - }.saveIn(updateStateJob) - } - - fun loadCharts(batchKeys: Set, interval: TrendInterval) { - 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 - val chartByInterval = when (interval) { - TrendInterval.H24 -> first.tokenCharts.h24 - TrendInterval.D7 -> first.tokenCharts.week - TrendInterval.M1 -> first.tokenCharts.month - } - chartByInterval != null - } - .map { batch -> batch.key } - .toSet() - - val batchesKeysToLoad = batchKeys.minus(alreadyLoadedChartsBatchKeys) - - if (batchesKeysToLoad.isNotEmpty()) { - actionsFlow.emit( - BatchAction.UpdateBatches( - keys = batchesKeysToLoad, - updateRequest = TokenMarketUpdateRequest.UpdateChart( - interval = interval.toBatchRequestInterval(), - currency = currentAppCurrency().code, - ), - async = true, - operationId = batchesKeysToLoad.toString() + interval.toString(), - ), - ) - } - } - } - - fun updateQuotes() { - modelScope.launch { - actionsFlow.emit( - BatchAction.CancelUpdates { - it.updateRequest is TokenMarketUpdateRequest.UpdateQuotes - }, - ) - - val batchKeys = batchFlow.state.value.data - .map { it.key } - .toSet() - actionsFlow.emit( - BatchAction.UpdateBatches( - keys = batchKeys, - updateRequest = TokenMarketUpdateRequest.UpdateQuotes( - currencyId = currentAppCurrency().code, - ), - async = true, - operationId = "update quotes", - ), - ) - } - } - - fun clearStateAndStopAllActions() { - resultBatches.value = ResultBatches() - modelScope.launch { - actionsFlow.emit(BatchAction.Reset) - } - } - - 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() - } - - fun getTokenById(id: CryptoCurrency.RawID): TokenMarket? { - val allTokens = batchFlow.state.value.data - .map { it.data } - .flatten() - return allTokens.find { it.id == id } - } - - private fun SortByTypeUM.toRequestOrder(): TokenMarketListConfig.Order { - return when (this) { - SortByTypeUM.Rating -> TokenMarketListConfig.Order.ByRating - SortByTypeUM.Trending -> TokenMarketListConfig.Order.Trending - SortByTypeUM.ExperiencedBuyers -> TokenMarketListConfig.Order.Buyers - SortByTypeUM.TopGainers -> TokenMarketListConfig.Order.TopGainers - SortByTypeUM.TopLosers -> TokenMarketListConfig.Order.TopLosers - SortByTypeUM.Staking -> TokenMarketListConfig.Order.Staking - SortByTypeUM.YieldSupply -> TokenMarketListConfig.Order.YieldSupply - } - } - - private fun TrendInterval.toBatchRequestInterval(): TokenMarketListConfig.Interval { - return when (this) { - TrendInterval.H24 -> TokenMarketListConfig.Interval.H24 - TrendInterval.D7 -> TokenMarketListConfig.Interval.WEEK - TrendInterval.M1 -> TokenMarketListConfig.Interval.MONTH - } - } - - private data class ResultBatches( - val uiBatches: List>> = emptyList(), - val processedItems: List>>? = null, - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt deleted file mode 100644 index 8c33b39d79..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/statemanager/MarketsListUMStateManager.kt +++ /dev/null @@ -1,260 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model.statemanager - -import androidx.compose.runtime.Stable -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.event.triggeredEvent -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM -import com.tangem.features.markets.tokenlist.impl.ui.state.* -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update - -@Stable -@Suppress("LongParameterList") -internal class MarketsListUMStateManager( - private val currentVisibleIds: Provider>, - private val onLoadMoreUiItems: () -> Unit, - private val visibleItemsChanged: (itemsKeys: List) -> Unit, - private val onRetryButtonClicked: () -> Unit, - private val onTokenClick: (MarketsListItemUM) -> Unit, - private val onShowTokensUnder100kClicked: () -> Unit, -) { - - val state = MutableStateFlow(createInitialState()) - val isInSearchStateFlow = state.map { it.searchBar.isActive }.distinctUntilChanged() - val searchQueryFlow = state.map { it.searchBar.query }.distinctUntilChanged() - - private var isSortByBottomSheetShown - get() = state.value.sortByBottomSheet.isShown - set(value) = state.update { it.copy(sortByBottomSheet = it.sortByBottomSheet.copy(isShown = value)) } - - var searchQuery - get() = state.value.searchBar.query - private set(value) = state.update { currentState -> - currentState.copy( - searchBar = currentState.searchBar.copy( - query = value, - isActive = value.isNotEmpty(), - ), - ) - } - - var isInSearchState - get() = state.value.searchBar.isActive - private set(value) = state.update { it.copy(searchBar = it.searchBar.copy(isActive = value)) } - - var selectedSortByType - get() = state.value.selectedSortBy - set(value) = state.update { currentState -> - currentState.copy( - selectedSortBy = value, - sortByBottomSheet = currentState.sortByBottomSheet.copy( - content = (currentState.sortByBottomSheet.content as SortByBottomSheetContentUM).copy( - selectedOption = value, - ), - ), - list = if (currentState.list is ListUM.Content && currentState.selectedSortBy != value) { - currentState.list.copy( - triggerScrollReset = triggeredEvent(Unit) { consumeTriggerResetScrollEvent() }, - ) - } else { - currentState.list - }, - ) - } - - var selectedInterval - get() = state.value.selectedInterval - set(value) = state.update { currentState -> - currentState.copy( - selectedInterval = value, - list = if (currentState.list is ListUM.Content && - currentState.selectedSortBy != SortByTypeUM.Rating && - currentState.selectedInterval != value - ) { - currentState.list.copy( - triggerScrollReset = triggeredEvent(Unit) { consumeTriggerResetScrollEvent() }, - ) - } else { - currentState.list - }, - ) - } - - fun onUiItemsChanged( - isInErrorState: Boolean, - isSearchNotFound: Boolean, - uiItems: ImmutableList, - marketsNotificationUM: MarketsNotificationUM?, - ) { - state.update { currentState -> - when { - isInErrorState -> { - currentState.copy( - list = ListUM.LoadingError(onRetryClicked = onRetryButtonClicked), - ) - } - isSearchNotFound -> { - currentState.copy(list = ListUM.SearchNothingFound) - } - uiItems.isEmpty() -> { - currentState.copy(list = ListUM.Loading) - } - else -> { - currentState.updateItems( - newItems = uiItems, - marketsNotificationUM = marketsNotificationUM, - ) - } - } - } - } - - private fun MarketsListUM.updateItems( - newItems: ImmutableList, - marketsNotificationUM: MarketsNotificationUM?, - ): MarketsListUM { - val currentState = this - - if (isInSearchMode.not() || currentState.showUnder100kButtonAlreadyPressed()) { - val itemsWithFilteredPriceChange = newItems.filterPriceChangeByVisibility() - - return currentState.copy( - list = generalContentState(itemsWithFilteredPriceChange) - .copy( - wasUnder100kTokensNotificationHidden = currentState.showUnder100kButtonAlreadyPressed(), - ), - marketsNotificationUM = marketsNotificationUM, - ) - } - - // Search state cases - - val filtered = newItems.filter { item -> item.isUnder100kMarketCap.not() } - .toImmutableList() - .filterPriceChangeByVisibility() - - if (filtered.size != newItems.size) { - val searchUiItemsCached = newItems.filterPriceChangeByVisibility() - - return currentState.copy( - list = generalContentState(filtered).copy( - wasUnder100kTokensNotificationHidden = false, - shouldShowUnder100kTokensNotification = true, - onShowTokensUnder100kClicked = { - onShowTokensUnder100kClicked() - state.update { s -> - (s.list as? ListUM.Content)?.let { - s.copy( - list = s.list.copy( - items = searchUiItemsCached, - shouldShowUnder100kTokensNotification = false, - wasUnder100kTokensNotificationHidden = true, - ), - ) - } ?: s - } - }, - ), - ) - } else { - return currentState.copy( - list = generalContentState(newItems.filterPriceChangeByVisibility()), - ) - } - } - - private fun MarketsListUM.showUnder100kButtonAlreadyPressed(): Boolean { - return this.list is ListUM.Content && this.isInSearchMode && this.list.wasUnder100kTokensNotificationHidden - } - - // Show price change animation for visible items only - private fun ImmutableList.filterPriceChangeByVisibility(): ImmutableList { - val visibleItemIds = currentVisibleIds() - return map { item -> - item.copy( - price = item.price.copy( - changeType = if (visibleItemIds.contains(item.id)) { - item.price.changeType - } else { - null - }, - ), - ) - }.toImmutableList() - } - - private fun generalContentState(newItems: ImmutableList): ListUM.Content { - return ListUM.Content( - items = newItems, - loadMore = onLoadMoreUiItems, - visibleIdsChanged = visibleItemsChanged, - shouldShowUnder100kTokensNotification = false, - onShowTokensUnder100kClicked = {}, - triggerScrollReset = consumedEvent(), - onItemClick = onTokenClick, - wasUnder100kTokensNotificationHidden = false, - ) - } - - private fun onBottomSheetOptionClicked(sortByTypeUM: SortByTypeUM) { - state.update { currentState -> - currentState.copy( - selectedSortBy = sortByTypeUM, - sortByBottomSheet = currentState.sortByBottomSheet.copy( - isShown = false, - content = (currentState.sortByBottomSheet.content as SortByBottomSheetContentUM).copy( - selectedOption = sortByTypeUM, - ), - ), - ) - } - } - - private fun consumeTriggerResetScrollEvent() { - state.update { currentState -> - currentState.copy( - list = if (currentState.list is ListUM.Content) { - currentState.list.copy( - triggerScrollReset = consumedEvent(), - ) - } else { - currentState.list - }, - ) - } - } - - private fun createInitialState(): MarketsListUM = MarketsListUM( - list = ListUM.Loading, - searchBar = SearchBarUM( - placeholderText = resourceReference(R.string.markets_search_header_title), - query = "", - onQueryChange = { searchQuery = it }, - isActive = false, - onActiveChange = { }, - ), - selectedSortBy = SortByTypeUM.Rating, - selectedInterval = MarketsListUM.TrendInterval.H24, - onIntervalClick = { selectedInterval = it }, - onSortByButtonClick = { isSortByBottomSheetShown = true }, - sortByBottomSheet = TangemBottomSheetConfig( - isShown = false, - onDismissRequest = { isSortByBottomSheetShown = false }, - content = SortByBottomSheetContentUM( - selectedOption = SortByTypeUM.Rating, - onOptionClicked = ::onBottomSheetOptionClicked, - ), - ), - marketsNotificationUM = null, - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/utils/LoggingUtils.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/utils/LoggingUtils.kt deleted file mode 100644 index 8b612f6db5..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/model/utils/LoggingUtils.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.model.utils - -import com.tangem.domain.markets.TokenMarket -import com.tangem.domain.markets.TokenMarketListConfig -import com.tangem.domain.markets.TokenMarketUpdateRequest -import com.tangem.pagination.BatchAction -import com.tangem.pagination.BatchUpdateResult -import com.tangem.pagination.PaginationStatus -import timber.log.Timber - -internal fun logStatus(tag: String, status: PaginationStatus>) { - Timber.tag(tag).d( - """ - Status - $status - """.trimIndent(), - ) -} - -internal fun logAction(tag: String, action: BatchAction) { - when (action) { - is BatchAction.Reload -> Timber.tag(tag).d( - """ - Reload = ${action.requestParams} - """.trimIndent(), - ) - is BatchAction.UpdateBatches -> Timber.tag(tag).d( - """ - To update: - keys: ${action.keys.toList()} - updateType: ${action.updateRequest.javaClass.simpleName} - """.trimIndent(), - ) - else -> Timber.tag(tag).d( - """ - $action - """.trimIndent(), - ) - } -} - -internal fun logUpdateResults( - tag: String, - updateResult: Pair>>, -) { - val sec = when (val s = updateResult.second) { - is BatchUpdateResult.Success -> "Success" - is BatchUpdateResult.Error -> s.throwable.toString() - } - - Timber.tag(tag).d( - """ - updateResults - request: ${updateResult.first} - result: $sec - """.trimIndent(), - ) -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt deleted file mode 100644 index 79b42c8c55..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt +++ /dev/null @@ -1,431 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui - -import android.content.res.Configuration -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.ripple -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.Keyboard -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH8 -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.components.buttons.SecondarySmallButton -import com.tangem.core.ui.components.buttons.SmallButtonConfig -import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons -import com.tangem.core.ui.components.fields.SearchBar -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.components.keyboardAsState -import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.extensions.* -import com.tangem.core.ui.res.LocalMainBottomSheetColor -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM -import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn -import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListSortByBottomSheet -import com.tangem.features.markets.tokenlist.impl.ui.components.YieldSupplyInMarketsPromoNotification -import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider -import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListUM -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetContentUM -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -private const val SHOW_MORE_KEY = "privacyPolicy" - -@Composable -internal fun MarketsList( - state: MarketsListUM, - onHeaderSizeChange: (Dp) -> Unit, - bottomSheetState: BottomSheetState, - modifier: Modifier = Modifier, -) { - val density = LocalDensity.current - val background = LocalMainBottomSheetColor.current.value - Column( - modifier = modifier - .fillMaxSize() - .imePadding() - .drawBehind { drawRect(background) }, - ) { - SearchBar( - modifier = Modifier - .drawBehind { drawRect(background) } - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = 8.dp, - ) - .onGloballyPositioned { layoutCoordinates -> - if (layoutCoordinates.size.height > 0) { - with(density) { - onHeaderSizeChange(layoutCoordinates.size.height.toDp()) - } - } - } - .padding(bottom = 4.dp), - state = state.searchBar, - ) - Content(state = state) - } - MarketsListSortByBottomSheet(config = state.sortByBottomSheet) - KeyboardEvents( - isSortByBottomSheetShown = state.sortByBottomSheet.isShown, - bottomSheetState = bottomSheetState, - ) -} - -@Composable -internal fun MarketsListWithBack( - state: MarketsListUM, - bottomSheetState: BottomSheetState, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val background = LocalMainBottomSheetColor.current.value - Column( - modifier = modifier - .fillMaxSize() - .imePadding() - .drawBehind { drawRect(background) }, - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = rememberVectorPainter( - ImageVector.vectorResource(R.drawable.ic_close_24), - ), - contentDescription = null, - tint = TangemTheme.colors.icon.primary1, - modifier = Modifier - .padding(16.dp) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false), - onClick = onBackClick, - ), - ) - SearchBar( - modifier = Modifier - .drawBehind { drawRect(background) } - .padding( - end = 16.dp, - ), - state = state.searchBar, - ) - } - Content(state = state) - } - MarketsListSortByBottomSheet(config = state.sortByBottomSheet) - KeyboardEvents( - isSortByBottomSheetShown = state.sortByBottomSheet.isShown, - bottomSheetState = bottomSheetState, - ) -} - -@Suppress("LongMethod") -@Composable -private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modifier) { - val strokeColor = TangemTheme.colors.stroke.primary - val scrolledState = remember { mutableStateOf(false) } - - Column(modifier.padding(horizontal = TangemTheme.dimens.size16)) { - AnimatedVisibility( - visible = scrolledState.value.not(), - ) { - Column { - SpacerH8() - Title(isInSearchMode = state.isInSearchMode) - SpacerH12() - } - } - Column { - AnimatedVisibility(state.isInSearchMode.not()) { - Options( - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), - sortByTypeUM = state.selectedSortBy, - trendInterval = state.selectedInterval, - onIntervalClick = state.onIntervalClick, - onSortByClick = state.onSortByButtonClick, - ) - } - - val marketsNotificationUM = state.marketsNotificationUM - AnimatedVisibility( - state.list !is ListUM.LoadingError && - state.isInSearchMode.not() && state.selectedSortBy != SortByTypeUM.YieldSupply, - ) { - val showMore = stringResourceSafe(R.string.common_show_more) - - when (marketsNotificationUM) { - is MarketsNotificationUM.YieldSupplyPromo -> { - val description = stringResourceSafe( - R.string.markets_yield_supply_banner_description, - showMore, - ) - - val clickableDescription = annotatedReference { - append(description.substringBefore(showMore)) - - pushStringAnnotation(SHOW_MORE_KEY, "") - appendColored(showMore, TangemTheme.colors.text.accent) - pop() - } - - YieldSupplyInMarketsPromoNotification( - config = marketsNotificationUM.config.copy( - subtitle = clickableDescription, - ), - modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), - ) - } - else -> { /* no-op */ - } - } - } - } - } - val strokeWidth = TangemTheme.dimens.size0_5 - Box( - Modifier - .fillMaxWidth() - .height(strokeWidth) - .drawBehind { - // draw horizontal line - if (scrolledState.value) { - drawLine( - color = strokeColor, - start = Offset(0f, size.height), - end = Offset(size.width, size.height), - strokeWidth = strokeWidth.toPx(), - ) - } - }, - ) - ItemsList( - scrolledState = scrolledState, - isInSearchMode = state.isInSearchMode, - state = state.list, - ) -} - -@Composable -private fun Title(isInSearchMode: Boolean, modifier: Modifier = Modifier) { - Text( - modifier = modifier, - text = if (isInSearchMode) { - stringResourceSafe(id = R.string.markets_search_result_title) - } else { - stringResourceSafe(id = R.string.markets_common_title) - }, - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, - ) -} - -@Composable -private fun Options( - sortByTypeUM: SortByTypeUM, - trendInterval: MarketsListUM.TrendInterval, - onSortByClick: () -> Unit, - onIntervalClick: (MarketsListUM.TrendInterval) -> Unit, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier - .height(IntrinsicSize.Max) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - SecondarySmallButton( - config = SmallButtonConfig( - text = sortByTypeUM.text, - onClick = onSortByClick, - icon = TangemButtonIconPosition.End(iconResId = R.drawable.ic_chevron_24), - ), - ) - SegmentedButtons( - config = persistentListOf( - MarketsListUM.TrendInterval.H24, - MarketsListUM.TrendInterval.D7, - MarketsListUM.TrendInterval.M1, - ), - color = TangemTheme.colors.button.secondary, - initialSelectedItem = trendInterval, - onClick = onIntervalClick, - modifier = Modifier - .width(160.dp) - .fillMaxHeight(), - ) { - Box( - Modifier - .fillMaxSize() - .align(Alignment.Center) - .padding( - vertical = TangemTheme.dimens.spacing4, - ), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = it.text.resolveReference(), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.primary1, - ) - } - } - } -} - -@Composable -private fun ItemsList( - scrolledState: MutableState, - isInSearchMode: Boolean, - state: ListUM, - modifier: Modifier = Modifier, -) { - val searchLazyListState = rememberLazyListState() - val mainLazyListState = rememberLazyListState() - - val isMainScrolled by remember { - derivedStateOf { - mainLazyListState.firstVisibleItemScrollOffset > 0 - } - } - - val isSearchScrolled by remember { - derivedStateOf { - searchLazyListState.firstVisibleItemScrollOffset > 0 - } - } - - LaunchedEffect(isMainScrolled, isInSearchMode, isSearchScrolled) { - scrolledState.value = if (isInSearchMode) { - isSearchScrolled - } else { - isMainScrolled - } - } - - MarketsListLazyColumn( - modifier = modifier, - state = state, - isInSearchMode = isInSearchMode, - lazyListState = if (isInSearchMode) { - searchLazyListState - } else { - mainLazyListState - }, - ) -} - -@Composable -private fun KeyboardEvents(isSortByBottomSheetShown: Boolean, bottomSheetState: BottomSheetState) { - val keyboardController = LocalSoftwareKeyboardController.current - val keyboard by keyboardAsState() - val focusManager = LocalFocusManager.current - - BackHandler(enabled = keyboard is Keyboard.Opened) { - keyboardController?.hide() - } - - LaunchedEffect(keyboard) { - if (keyboard is Keyboard.Closed) { - focusManager.clearFocus() - } - } - - LaunchedEffect(isSortByBottomSheetShown) { - keyboardController?.hide() - } - - LaunchedEffect(bottomSheetState) { - if (bottomSheetState == BottomSheetState.COLLAPSED) { - focusManager.clearFocus() - } - } -} - -//region: Preview - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview() { - TangemThemePreview(alwaysShowBottomSheets = false) { - val primaryBackground = TangemTheme.colors.background.primary - - CompositionLocalProvider( - LocalMainBottomSheetColor provides remember { mutableStateOf(primaryBackground) }, - ) { - MarketsList( - state = MarketsListUM( - list = ListUM.Content( - items = MarketChartListItemPreviewDataProvider().values - .flatMap { item -> List(size = 10) { item } } - .mapIndexed { index, item -> - item.copy(id = CryptoCurrency.RawID(index.toString())) - } - .toImmutableList(), - shouldShowUnder100kTokensNotification = false, - wasUnder100kTokensNotificationHidden = false, - loadMore = {}, - visibleIdsChanged = {}, - onShowTokensUnder100kClicked = {}, - triggerScrollReset = consumedEvent(), - onItemClick = {}, - ), - searchBar = SearchBarUM( - placeholderText = resourceReference(R.string.markets_search_header_title), - query = "", - onQueryChange = {}, - isActive = false, - onActiveChange = { }, - ), - selectedSortBy = SortByTypeUM.Rating, - selectedInterval = MarketsListUM.TrendInterval.H24, - onIntervalClick = {}, - onSortByButtonClick = {}, - sortByBottomSheet = TangemBottomSheetConfig( - isShown = false, - onDismissRequest = {}, - content = SortByBottomSheetContentUM(selectedOption = SortByTypeUM.Rating) {}, - ), - marketsNotificationUM = MarketsNotificationUM.YieldSupplyPromo( - onClick = {}, - onCloseClick = {}, - ), - ), - onHeaderSizeChange = {}, - bottomSheetState = BottomSheetState.EXPANDED, - ) - } - } -} - -//endregion: Preview \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt deleted file mode 100644 index 1854695357..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItem.kt +++ /dev/null @@ -1,324 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.RectangleShape -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 com.tangem.common.ui.charts.MarketChartMini -import com.tangem.common.ui.charts.state.MarketChartLook -import com.tangem.common.ui.charts.state.MarketChartRawData -import com.tangem.common.ui.tokens.TokenPriceText -import com.tangem.core.ui.components.* -import com.tangem.core.ui.components.currency.icon.CoinIcon -import com.tangem.core.ui.components.marketprice.PriceChangeInPercent -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.LocalWindowSize -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.test.MarketsTestTags -import com.tangem.core.ui.windowsize.WindowSizeType -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.preview.MarketChartListItemPreviewDataProvider -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM -import com.tangem.utils.StringsSigns.MINUS -import kotlin.random.Random - -@Composable -internal fun MarketsListItem(model: MarketsListItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { - MarketsListItemContent( - modifier = modifier - .fillMaxWidth() - .clip(RectangleShape) - .clickable(onClick = onClick) - .testTag(MarketsTestTags.TOKENS_LIST_ITEM), - model = model, - ) -} - -@Composable -private fun MarketsListItemContent(model: MarketsListItemUM, modifier: Modifier = Modifier) { - val windowSize = LocalWindowSize.current - - Row( - modifier = modifier.padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing15, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - CoinIcon( - modifier = Modifier.size(TangemTheme.dimens.size36), - url = model.iconUrl, - alpha = 1f, - colorFilter = null, - fallbackResId = R.drawable.ic_custom_token_44, - ) - - SpacerW12() - - Column(modifier = Modifier.weight(1f)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - TokenTitle( - modifier = Modifier.weight(1f, fill = false), - name = model.name, - currencySymbol = model.currencySymbol, - ) - SpacerW8() - TokenPriceText( - modifier = Modifier.alignByBaseline(), - price = model.price.text, - priceChangeType = model.price.changeType, - ) - } - - SpacerH(height = TangemTheme.dimens.spacing2) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom, - ) { - TokenSubtitle( - modifier = Modifier - .weight(1f, fill = false) - .alignByBaseline(), - ratingPosition = model.ratingPosition, - marketCap = model.marketCap, - stakingRate = model.stakingRate, - ) - PriceChangeInPercent( - modifier = Modifier.alignByBaseline(), - textStyle = TangemTheme.typography.caption2, - type = model.trendType, - valueInPercent = model.trendPercentText, - ) - } - } - - if (windowSize.widthAtLeast(WindowSizeType.Small)) { - Spacer(Modifier.width(TangemTheme.dimens.spacing10)) - - Chart( - chartType = model.chartType, - chartRawData = model.chartData, - ) - } - } -} - -@Composable -private fun TokenTitle(name: String, currencySymbol: String, modifier: Modifier = Modifier) { - Row(modifier = modifier) { - Text( - modifier = Modifier - .weight(1f, fill = false) - .alignByBaseline(), - text = name, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle2, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - SpacerW4() - Text( - modifier = Modifier.alignByBaseline(), - text = currencySymbol, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption1, - maxLines = 1, - overflow = TextOverflow.Visible, - ) - } -} - -@Composable -private fun TokenSubtitle( - ratingPosition: String?, - marketCap: String?, - stakingRate: TextReference?, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - TokenRatingPlace(ratingPosition = ratingPosition) - if (marketCap != null) { - SpacerW4() - TokenMarketCapText( - modifier = Modifier.weight(1f, fill = false), - text = marketCap, - ) - } - if (stakingRate != null) { - SpacerW4() - StakingRate(stakingRate = stakingRate.resolveReference()) - } - } -} - -@Composable -private fun RowScope.TokenRatingPlace(ratingPosition: String?) { - Box( - modifier = Modifier - .alignByBaseline() - .heightIn(min = TangemTheme.dimens.size16) - .background( - color = TangemTheme.colors.field.primary, - shape = TangemTheme.shapes.roundedCornersSmall2, - ) - .padding(horizontal = TangemTheme.dimens.spacing5), - ) { - Text( - text = ratingPosition ?: MINUS, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption1, - maxLines = 1, - ) - } -} - -@Composable -private fun RowScope.StakingRate(stakingRate: String) { - Box( - modifier = Modifier - .alignByBaseline() - .heightIn(min = TangemTheme.dimens.size16) - .border( - width = TangemTheme.dimens.size1, - color = TangemTheme.colors.field.primary, - shape = TangemTheme.shapes.roundedCornersSmall2, - ) - .padding(horizontal = TangemTheme.dimens.spacing5), - ) { - Text( - text = stakingRate, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption1, - maxLines = 1, - ) - } -} - -@Composable -private fun RowScope.TokenMarketCapText(text: String, modifier: Modifier = Modifier) { - Text( - modifier = modifier.alignByBaseline(), - text = text, - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption2, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) -} - -@Composable -private fun Chart(chartType: MarketChartLook.Type, chartRawData: MarketChartRawData?) { - val chartWidth = TangemTheme.dimens.size56 - Box( - modifier = Modifier - .padding(vertical = TangemTheme.dimens.spacing2) - .size(height = TangemTheme.dimens.size24, width = chartWidth), - ) { - if (chartRawData != null) { - MarketChartMini( - rawData = chartRawData, - type = chartType, - ) - } else { - RectangleShimmer( - modifier = Modifier - .fillMaxWidth() - .height(TangemTheme.dimens.size12) - .align(Alignment.Center), - radius = TangemTheme.dimens.radius3, - ) - } - } -} - -// region preview -@Preview(showBackground = true, widthDp = 360, name = "normal") -@Preview(showBackground = true, widthDp = 360, name = "normal night", uiMode = Configuration.UI_MODE_NIGHT_YES) -@Preview(showBackground = true, widthDp = 260, name = "small width") -@Composable -private fun Preview(@PreviewParameter(MarketChartListItemPreviewDataProvider::class) state: MarketsListItemUM) { - TangemThemePreview { - var state1 by remember { mutableStateOf(state) } - var state2 by remember { mutableStateOf(state) } - var prices by remember { - mutableStateOf( - listOf( - 100 to PriceChangeType.NEUTRAL, - 200 to PriceChangeType.NEUTRAL, - ), - ) - } - - Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { - MarketsListItem( - modifier = Modifier, - model = state1, - ) - MarketsListItem( - modifier = Modifier, - model = state2, - ) - Row { - Button( - onClick = { - state1 = state1.copy( - trendType = PriceChangeType.entries.random(), - ) - state2 = state2.copy( - trendType = PriceChangeType.entries.random(), - ) - }, - ) { Text(text = "trend") } - - Button( - onClick = { - prices = prices.map { priceInfo -> - if (Random.nextBoolean()) { - priceInfo.first.inc() to PriceChangeType.UP - } else { - priceInfo.first.dec() to PriceChangeType.DOWN - } - } - state1 = state1.copy( - price = MarketsListItemUM.Price( - text = "0.${prices[0].first}023 $", - changeType = prices[0].second, - ), - ) - state2 = state2.copy( - price = MarketsListItemUM.Price( - text = "0.${prices[1].first}023 $", - changeType = prices[1].second, - ), - ) - }, - ) { Text(text = "price") } - } - } - } -} - -// endregion preview \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItemPlaceholder.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItemPlaceholder.kt deleted file mode 100644 index 692b663cc8..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListItemPlaceholder.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.LocalWindowSize -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.core.ui.windowsize.WindowSizeType - -@Suppress("LongMethod") -@Composable -fun MarketsListItemPlaceholder() { - val density = LocalDensity.current - val windowSize = LocalWindowSize.current - val sp12 = with(density) { 12.sp.toDp() } - - Row( - modifier = Modifier.padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing15, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - CircleShimmer(Modifier.size(TangemTheme.dimens.size36)) - - SpacerW12() - - Column(modifier = Modifier.weight(1f)) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = TangemTheme.dimens.spacing4), - contentAlignment = Alignment.CenterStart, - ) { - RectangleShimmer( - modifier = Modifier - .width(TangemTheme.dimens.size70) - .height(sp12), - radius = TangemTheme.dimens.radius3, - ) - } - - SpacerH(height = TangemTheme.dimens.spacing2) - - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = TangemTheme.dimens.spacing2), - contentAlignment = Alignment.CenterStart, - ) { - RectangleShimmer( - modifier = Modifier - .width(TangemTheme.dimens.size52) - .height(sp12), - radius = TangemTheme.dimens.radius3, - ) - } - } - - if (windowSize.widthAtLeast(WindowSizeType.Small)) { - Spacer(Modifier.width(TangemTheme.dimens.spacing10)) - - Box { - RectangleShimmer( - modifier = Modifier - .align(Alignment.Center) - .width(TangemTheme.dimens.size56) - .height(TangemTheme.dimens.size12), - radius = TangemTheme.dimens.radius3, - ) - } - } - } -} - -@Preview(showBackground = true, widthDp = 360, name = "normal") -@Preview(showBackground = true, widthDp = 360, name = "normal night", uiMode = Configuration.UI_MODE_NIGHT_YES) -@Preview(showBackground = true, widthDp = 320, name = "small width") -@Composable -private fun Preview() { - TangemThemePreview { - Column(Modifier.background(TangemTheme.colors.background.tertiary)) { - repeat(20) { - MarketsListItemPlaceholder() - } - } - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt deleted file mode 100644 index b38a9cd3d9..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListLazyColumn.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.testTag -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.UnableToLoadData -import com.tangem.core.ui.components.buttons.SecondarySmallButton -import com.tangem.core.ui.components.buttons.SmallButtonConfig -import com.tangem.core.ui.components.list.InfiniteListHandler -import com.tangem.core.ui.event.EventEffect -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.test.MarketsTestTags -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM.Companion.TOKEN_LAZY_LIST_ID_SEPARATOR -import kotlinx.coroutines.launch - -private const val LOAD_NEXT_PAGE_ON_END_INDEX = 50 -private const val LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH = 25 - -@Composable -@Suppress("LongMethod") -internal fun MarketsListLazyColumn( - state: ListUM, - isInSearchMode: Boolean, - lazyListState: LazyListState, - modifier: Modifier = Modifier, -) { - val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - val coroutineScope = rememberCoroutineScope() - - SideEffect { - if (state is ListUM.Loading) { - coroutineScope.launch { - lazyListState.scrollToItem(0) - } - } - } - - if (state is ListUM.Content) { - EventEffect(state.triggerScrollReset) { - lazyListState.scrollToItem(0) - } - } - - if (state is ListUM.Loading) { - LazyColumn( - modifier = modifier, - state = rememberLazyListState(), - contentPadding = PaddingValues(bottom = bottomBarHeight), - userScrollEnabled = false, - ) { - items(count = 100, key = { it }) { - MarketsListItemPlaceholder() - } - } - } else { - LazyColumn( - modifier = modifier.testTag(MarketsTestTags.TOKENS_LIST), - state = lazyListState, - contentPadding = PaddingValues(bottom = bottomBarHeight), - userScrollEnabled = true, - ) { - // ATTENTION! There should be no elements with a string key value except MarketsListItem! - when (state) { - is ListUM.LoadingError -> { - item(key = "loading error".hashCode()) { - LoadingErrorItem( - modifier = Modifier.fillParentMaxSize(), - onTryAgain = state.onRetryClicked, - ) - } - } - ListUM.SearchNothingFound -> { - item(key = "not found text".hashCode()) { - SearchNothingFoundText( - modifier = Modifier.fillParentMaxSize(), - ) - } - } - is ListUM.Content -> { - items( - items = state.items, - key = { it.getComposeKey() }, - ) { item -> - MarketsListItem( - model = item, - onClick = { state.onItemClick(item) }, - ) - } - - if (isInSearchMode && state.shouldShowUnder100kTokensNotification) { - item(key = "show tokens under 100k".hashCode()) { - ShowTokensUnder100kItem( - onShowTokensClick = state.onShowTokensUnder100kClicked, - ) - } - } - } - else -> {} - } - } - } - - VisibleItemsTracker(lazyListState, state) - - InfiniteListHandler( - listState = lazyListState, - buffer = if (isInSearchMode) { - LOAD_NEXT_PAGE_ON_END_INDEX_SEARCH - } else { - LOAD_NEXT_PAGE_ON_END_INDEX - }, - triggerLoadMoreCheckOnItemsCountChange = true, - onLoadMore = remember(state) { - { - if (state is ListUM.Content && state.shouldShowUnder100kTokensNotification.not()) { - state.loadMore() - true - } else { - false - } - } - }, - ) -} - -@Composable -private fun LoadingErrorItem(onTryAgain: () -> Unit, modifier: Modifier = Modifier) { - Box( - modifier - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing12, - ) - .fillMaxWidth(), - contentAlignment = Alignment.Center, - ) { - UnableToLoadData(onRetryClick = onTryAgain) - } -} - -@Composable -private fun ShowTokensUnder100kItem(onShowTokensClick: () -> Unit, modifier: Modifier = Modifier) { - Column( - modifier = modifier - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing12, - ) - .fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = stringResourceSafe(R.string.markets_search_see_tokens_under_100k), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, - ) - SecondarySmallButton( - config = SmallButtonConfig( - text = resourceReference(R.string.markets_search_show_tokens), - onClick = onShowTokensClick, - ), - ) - SpacerH12() - } -} - -@Composable -private fun SearchNothingFoundText(modifier: Modifier = Modifier) { - Box( - modifier = modifier, - contentAlignment = Alignment.Center, - ) { - Text( - text = stringResourceSafe(R.string.markets_search_token_no_result_title), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, - ) - } -} - -@Composable -private fun VisibleItemsTracker(listState: LazyListState, state: ListUM) { - val visibleItems by remember { - derivedStateOf { - listState.layoutInfo.visibleItemsInfo.mapNotNull { item -> - (item.key as? String)?.split(TOKEN_LAZY_LIST_ID_SEPARATOR)?.first() - ?.let { rawId -> CryptoCurrency.RawID(rawId) } - } - } - } - - LaunchedEffect(listState.isScrollInProgress, visibleItems) { - if (state is ListUM.Content && listState.isScrollInProgress.not()) { - state.visibleIdsChanged(visibleItems) - } - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt deleted file mode 100644 index d862db0b01..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/MarketsListSortByBottomSheet.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.inputrow.InputRowChecked -import com.tangem.core.ui.components.inputrow.inner.DividerContainer -import com.tangem.core.ui.decorations.roundedShapeItemDecoration -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByBottomSheetContentUM -import com.tangem.features.markets.tokenlist.impl.ui.state.SortByTypeUM - -@Composable -fun MarketsListSortByBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet( - config = config, - titleText = resourceReference(R.string.markets_sort_by_title), - containerColor = TangemTheme.colors.background.tertiary, - content = { Content(it) }, - ) -} - -@Composable -private fun Content(content: SortByBottomSheetContentUM) { - Column( - modifier = Modifier - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - SortByTypeUM.entries.forEachIndexed { index, type -> - DividerContainer( - modifier = Modifier - .roundedShapeItemDecoration( - currentIndex = index, - lastIndex = SortByTypeUM.entries.lastIndex, - addDefaultPadding = false, - ) - .background(TangemTheme.colors.background.action) - .clickable { content.onOptionClicked(type) }, - showDivider = index != SortByTypeUM.entries.lastIndex, - ) { - InputRowChecked( - text = type.text, - checked = type == content.selectedOption, - ) - } - } - } -} - -@Preview(widthDp = 360, heightDp = 640) -@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview() { - TangemThemePreview( - alwaysShowBottomSheets = true, - ) { - Box(Modifier.background(TangemTheme.colors.background.secondary)) { - MarketsListSortByBottomSheet( - TangemBottomSheetConfig( - isShown = true, - onDismissRequest = {}, - content = SortByBottomSheetContentUM( - selectedOption = SortByTypeUM.Trending, - onOptionClicked = {}, - ), - ), - ) - } - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/StakingInMarketsPromoNotification.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/StakingInMarketsPromoNotification.kt deleted file mode 100644 index 9026a0ebb4..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/StakingInMarketsPromoNotification.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import android.content.res.Configuration -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.R -import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.notifications.CloseableIconButton -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveAnnotatedReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview - -private val bgColor = Color(0x1F8CD9FF) -private val borderColor = Color(0x3D8CD9FF) - -@Composable -fun StakingInMarketsPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) { - var textHeightDp by remember { mutableStateOf(0.dp) } - - Box( - modifier = modifier - .fillMaxWidth() - .border( - width = 1.dp, - shape = TangemTheme.shapes.roundedCornersXMedium, - color = borderColor, - ) - .clip(shape = TangemTheme.shapes.roundedCornersXMedium) - .background(bgColor) - .clickable { config.onClick?.invoke() }, - ) { - PromoImage( - iconRes = config.iconResId, - modifier = Modifier.height(textHeightDp), - ) - PromoText( - title = config.title, - subtitle = config.subtitle, - onSizeChange = { textHeightDp = it }, - ) - CloseableIconButton( - onClick = config.onCloseClick, - modifier = Modifier.align(alignment = Alignment.TopEnd), - iconTint = TangemTheme.colors.icon.secondary, - ) - } -} - -@Composable -private fun PromoImage(@DrawableRes iconRes: Int, modifier: Modifier = Modifier) { - Box( - modifier = modifier.padding(12.dp), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = iconRes), - contentDescription = null, - contentScale = ContentScale.FillWidth, - modifier = Modifier - .requiredWidth(56.dp) - .wrapContentHeight(Alignment.Top, unbounded = true), - ) - } -} - -@Composable -private fun PromoText(title: TextReference?, subtitle: TextReference, onSizeChange: (Dp) -> Unit) { - val density = LocalDensity.current - - Box( - modifier = Modifier.onSizeChanged { - with(density) { onSizeChange(it.height.toDp()) } - }, - ) { - TextsBlock( - title = title, - subtitle = subtitle, - modifier = Modifier - .wrapContentHeight() - .align(Alignment.CenterStart) - .padding(start = 76.dp, top = 12.dp, end = 12.dp, bottom = 12.dp), - ) - } -} - -@Composable -private fun TextsBlock(title: TextReference?, subtitle: TextReference, modifier: Modifier = Modifier) { - Column(modifier = modifier) { - val titleText = title?.resolveReference() - - if (titleText != null) { - Text( - text = titleText, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.button, - ) - - SpacerH(height = TangemTheme.dimens.spacing2) - } - - Text( - text = subtitle.resolveAnnotatedReference(), - color = TangemTheme.colors.text.secondary, - style = TangemTheme.typography.caption2, - ) - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun RingPromoNotification_Preview() { - TangemThemePreview { - StakingInMarketsPromoNotification( - config = NotificationConfig( - title = stringReference("Earn up to 14% APY"), - subtitle = stringReference("Staking is the easiest way to earn rewards on your crypto. Show more"), - iconResId = R.drawable.img_staking_in_market_notification, - onCloseClick = { }, - ), - ) - } -} - -// endregion \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt deleted file mode 100644 index e2f16f2735..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/components/YieldSupplyInMarketsPromoNotification.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.components - -import android.content.res.Configuration -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.tangem.core.ui.R -import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.notifications.CloseableIconButton -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveAnnotatedReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview - -private val bgColor = Color(0x2684B0D7) -private val borderColor = Color(0x2684B0D7) - -@Composable -fun YieldSupplyInMarketsPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) { - var textHeightDp by remember { mutableStateOf(0.dp) } - - Box( - modifier = modifier - .fillMaxWidth() - .border( - width = 1.dp, - shape = TangemTheme.shapes.roundedCornersXMedium, - color = borderColor, - ) - .clip(shape = TangemTheme.shapes.roundedCornersXMedium) - .background(bgColor) - .clickable { config.onClick?.invoke() }, - ) { - PromoImage( - iconRes = config.iconResId, - modifier = Modifier.height(textHeightDp), - ) - PromoText( - title = config.title, - subtitle = config.subtitle, - onSizeChange = { textHeightDp = it }, - ) - CloseableIconButton( - onClick = config.onCloseClick, - modifier = Modifier.align(alignment = Alignment.TopEnd), - iconTint = TangemTheme.colors.icon.secondary, - ) - } -} - -@Composable -private fun PromoImage(@DrawableRes iconRes: Int, modifier: Modifier = Modifier) { - Box( - modifier = modifier.padding(vertical = 8.dp), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = iconRes), - contentDescription = null, - contentScale = ContentScale.FillWidth, - modifier = Modifier - .requiredWidth(80.dp) - .wrapContentHeight(Alignment.CenterVertically, unbounded = true), - ) - } -} - -@Composable -private fun PromoText(title: TextReference?, subtitle: TextReference, onSizeChange: (Dp) -> Unit) { - val density = LocalDensity.current - - Box( - modifier = Modifier.onSizeChanged { - with(density) { onSizeChange(it.height.toDp()) } - }, - ) { - TextsBlock( - title = title, - subtitle = subtitle, - modifier = Modifier - .wrapContentHeight() - .align(Alignment.CenterStart) - .padding(start = 80.dp, top = 12.dp, end = 12.dp, bottom = 12.dp), - ) - } -} - -@Composable -private fun TextsBlock(title: TextReference?, subtitle: TextReference, modifier: Modifier = Modifier) { - Column(modifier = modifier) { - val titleText = title?.resolveReference() - - if (titleText != null) { - Text( - text = titleText, - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.button, - ) - - SpacerH(height = TangemTheme.dimens.spacing2) - } - - Text( - text = subtitle.resolveAnnotatedReference(), - color = TangemTheme.colors.text.secondary, - style = TangemTheme.typography.caption2, - ) - } -} - -// region Preview -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun RingPromoNotification_Preview() { - TangemThemePreview { - YieldSupplyInMarketsPromoNotification( - config = NotificationConfig( - title = stringReference("Activate Yield Mode"), - subtitle = stringReference("Power up your assets while supplying them with instant access. Show more"), - iconResId = R.drawable.img_yield_supply_in_market_notification, - onCloseClick = { }, - ), - ) - } -} - -// endregion \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt deleted file mode 100644 index 16d1279d95..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt +++ /dev/null @@ -1,116 +0,0 @@ -@file:Suppress("MagicNumber") - -package com.tangem.features.markets.tokenlist.impl.ui.preview - -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.common.ui.charts.state.MarketChartRawData -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM -import kotlinx.collections.immutable.persistentListOf - -internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParameterProvider( - collection = listOf( - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin", - currencySymbol = "BTC", - iconUrl = "", - ratingPosition = "10", - marketCap = "$6.233 B", - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.UP, - chartData = MarketChartRawData( - y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0), - ), - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin", - currencySymbol = "BTC", - iconUrl = null, - ratingPosition = "10", - marketCap = "$6.233 B", - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.NEUTRAL, - chartData = null, - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin", - currencySymbol = "BTC", - iconUrl = null, - ratingPosition = "10", - marketCap = "$6.23348172384781234 B", - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.DOWN, - chartData = MarketChartRawData( - y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0), - ), - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin", - currencySymbol = "BTC", - iconUrl = null, - ratingPosition = "10", - marketCap = null, - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.UP, - chartData = MarketChartRawData( - y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0), - ), - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin", - currencySymbol = "BTC", - iconUrl = null, - ratingPosition = null, - marketCap = "$6.233 B", - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.UP, - chartData = MarketChartRawData( - y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0), - ), - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - MarketsListItemUM( - id = CryptoCurrency.RawID("1"), - name = "Bitcoin", - currencySymbol = "BTC", - iconUrl = null, - ratingPosition = null, - marketCap = null, - price = MarketsListItemUM.Price(text = "31 285.72$"), - trendPercentText = "12.43%", - trendType = PriceChangeType.UP, - chartData = MarketChartRawData( - y = persistentListOf(0.4, 0.2, 0.4, 0.1, 0.4, 2.0, 5.0, 0.1, 2.0, 2.0, 3.0), - ), - isUnder100kMarketCap = false, - stakingRate = stringReference("APY 12.34%"), - updateTimestamp = 0, - ), - ), -) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt deleted file mode 100644 index 58b80368b4..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.state - -import androidx.compose.runtime.Immutable -import com.tangem.common.ui.charts.state.MarketChartLook -import com.tangem.common.ui.charts.state.MarketChartRawData -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.extensions.TextReference -import com.tangem.domain.models.currency.CryptoCurrency - -@Immutable -data class MarketsListItemUM( - val id: CryptoCurrency.RawID, - val name: String, - val currencySymbol: String, - val iconUrl: String?, - val ratingPosition: String?, - val marketCap: String?, - val price: Price, - val trendPercentText: String, - val trendType: PriceChangeType, - val chartData: MarketChartRawData?, - val isUnder100kMarketCap: Boolean, - val stakingRate: TextReference?, - val updateTimestamp: Long?, -) { - val chartType: MarketChartLook.Type = when (trendType) { - PriceChangeType.UP -> MarketChartLook.Type.Growing - PriceChangeType.DOWN -> MarketChartLook.Type.Falling - PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral - } - - @Immutable - data class Price( - val text: String, - val changeType: PriceChangeType? = null, - ) - - fun getComposeKey(): String { - return id.value + TOKEN_LAZY_LIST_ID_SEPARATOR + marketCap.orEmpty() + updateTimestamp - } - - companion object { - const val TOKEN_LAZY_LIST_ID_SEPARATOR = "@" - } -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt deleted file mode 100644 index d0ade846a6..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListUM.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.state - -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.features.markets.impl.R -import com.tangem.features.markets.tokenlist.impl.model.MarketsNotificationUM -import kotlinx.collections.immutable.ImmutableList - -internal data class MarketsListUM( - val list: ListUM, - val searchBar: SearchBarUM, - val selectedSortBy: SortByTypeUM, - val sortByBottomSheet: TangemBottomSheetConfig, - val selectedInterval: TrendInterval, - val onIntervalClick: (TrendInterval) -> Unit, - val onSortByButtonClick: () -> Unit, - val marketsNotificationUM: MarketsNotificationUM?, -) { - val isInSearchMode - get() = searchBar.isActive - - enum class TrendInterval(val text: TextReference) { - H24(resourceReference(R.string.markets_selector_interval_24h_title)), - D7(resourceReference(R.string.markets_selector_interval_7d_title)), - M1(resourceReference(R.string.markets_selector_interval_1m_title)), - } -} - -enum class SortByTypeUM(val text: TextReference) { - Rating(resourceReference(R.string.markets_sort_by_rating_title)), - Trending(resourceReference(R.string.markets_sort_by_trending_title)), - ExperiencedBuyers(resourceReference(R.string.markets_sort_by_experienced_buyers_title)), - TopGainers(resourceReference(R.string.markets_sort_by_top_gainers_title)), - TopLosers(resourceReference(R.string.markets_sort_by_top_losers_title)), - Staking(resourceReference(R.string.common_staking)), - YieldSupply(resourceReference(R.string.yield_module_earn_sheet_title)), -} - -@Immutable -sealed class ListUM { - - data class Content( - val items: ImmutableList, - val shouldShowUnder100kTokensNotification: Boolean, - val wasUnder100kTokensNotificationHidden: Boolean, - val loadMore: () -> Unit, - val visibleIdsChanged: (List) -> Unit, - val onShowTokensUnder100kClicked: () -> Unit, - val triggerScrollReset: StateEvent, - val onItemClick: (MarketsListItemUM) -> Unit, - ) : ListUM() - - data object Loading : ListUM() - - data class LoadingError( - val onRetryClicked: () -> Unit, - ) : ListUM() - - data object SearchNothingFound : ListUM() -} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt deleted file mode 100644 index f3b4e9682b..0000000000 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/SortByBottomSheetContentUM.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.features.markets.tokenlist.impl.ui.state - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent - -data class SortByBottomSheetContentUM( - val selectedOption: SortByTypeUM, - val onOptionClicked: (SortByTypeUM) -> Unit, -) : TangemBottomSheetConfigContent \ No newline at end of file