Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-12 08:05:42 +01:00
parent 4aa267c951
commit 0548571089
41 changed files with 573 additions and 347 deletions

View file

@ -45,4 +45,14 @@ object EarnDomainModule {
fun provideGetEarnTokensBatchFlowUseCase(repository: EarnRepository): GetEarnTokensBatchFlowUseCase { fun provideGetEarnTokensBatchFlowUseCase(repository: EarnRepository): GetEarnTokensBatchFlowUseCase {
return GetEarnTokensBatchFlowUseCase(repository) return GetEarnTokensBatchFlowUseCase(repository)
} }
@Provides
fun provideGetEarnFilterUseCase(repository: EarnRepository): GetEarnFilterUseCase {
return GetEarnFilterUseCase(repository)
}
@Provides
fun provideSetEarnFilterUseCase(repository: EarnRepository): SetEarnFilterUseCase {
return SetEarnFilterUseCase(repository)
}
} }

View file

@ -0,0 +1,17 @@
package com.tangem.data.earn.datastore
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.domain.earn.model.EarnFilter
import com.tangem.domain.earn.model.EarnFilterNetwork
import com.tangem.domain.earn.model.EarnFilterType
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class EarnFilterStore @Inject constructor() :
RuntimeStateStore<EarnFilter> by RuntimeStateStore(
defaultValue = EarnFilter(
earnFilterNetwork = EarnFilterNetwork.AllNetworks(isSelected = true),
earnFilterType = EarnFilterType.ALL,
),
)

View file

@ -6,7 +6,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class EarnNetworksStore @Inject constructor() : internal class EarnNetworksStore @Inject constructor() :
RuntimeStateStore<EarnNetworks?> by RuntimeStateStore( RuntimeStateStore<EarnNetworks?> by RuntimeStateStore(
defaultValue = null, defaultValue = null,
) )

View file

@ -6,7 +6,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class EarnTopTokensStore @Inject constructor() : internal class EarnTopTokensStore @Inject constructor() :
RuntimeStateStore<EarnTopToken?> by RuntimeStateStore( RuntimeStateStore<EarnTopToken?> by RuntimeStateStore(
defaultValue = null, defaultValue = null,
) )

View file

@ -2,6 +2,7 @@ package com.tangem.data.earn.di
import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.data.earn.DefaultEarnErrorResolver import com.tangem.data.earn.DefaultEarnErrorResolver
import com.tangem.data.earn.datastore.EarnFilterStore
import com.tangem.data.earn.datastore.EarnNetworksStore import com.tangem.data.earn.datastore.EarnNetworksStore
import com.tangem.data.earn.datastore.EarnTopTokensStore import com.tangem.data.earn.datastore.EarnTopTokensStore
import com.tangem.data.earn.repository.DefaultEarnRepository import com.tangem.data.earn.repository.DefaultEarnRepository
@ -31,6 +32,7 @@ internal object EarnDataModule {
userWalletsListRepository: UserWalletsListRepository, userWalletsListRepository: UserWalletsListRepository,
earnNetworksStore: EarnNetworksStore, earnNetworksStore: EarnNetworksStore,
earnTopTokensStore: EarnTopTokensStore, earnTopTokensStore: EarnTopTokensStore,
earnFilterStore: EarnFilterStore,
earnErrorResolver: EarnErrorResolver, earnErrorResolver: EarnErrorResolver,
excludedBlockchains: ExcludedBlockchains, excludedBlockchains: ExcludedBlockchains,
): EarnRepository { ): EarnRepository {
@ -42,6 +44,7 @@ internal object EarnDataModule {
earnTopTokensStore = earnTopTokensStore, earnTopTokensStore = earnTopTokensStore,
earnErrorResolver = earnErrorResolver, earnErrorResolver = earnErrorResolver,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,
earnFilterStore = earnFilterStore,
) )
} }
} }

View file

@ -6,6 +6,7 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.data.common.currency.CryptoCurrencyFactory import com.tangem.data.common.currency.CryptoCurrencyFactory
import com.tangem.data.earn.converter.EarnTokenConverter import com.tangem.data.earn.converter.EarnTokenConverter
import com.tangem.data.earn.datastore.EarnFilterStore
import com.tangem.data.earn.datastore.EarnNetworksStore import com.tangem.data.earn.datastore.EarnNetworksStore
import com.tangem.data.earn.datastore.EarnTopTokensStore import com.tangem.data.earn.datastore.EarnTopTokensStore
import com.tangem.data.earn.repository.batch.EarnTokensBatchFetcher import com.tangem.data.earn.repository.batch.EarnTokensBatchFetcher
@ -14,6 +15,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.EarnResponse import com.tangem.datasource.api.tangemTech.models.EarnResponse
import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.earn.EarnErrorResolver import com.tangem.domain.earn.EarnErrorResolver
import com.tangem.domain.earn.model.EarnFilter
import com.tangem.domain.earn.model.EarnTokensBatchFlow import com.tangem.domain.earn.model.EarnTokensBatchFlow
import com.tangem.domain.earn.model.EarnTokensBatchingContext import com.tangem.domain.earn.model.EarnTokensBatchingContext
import com.tangem.domain.earn.repository.EarnRepository import com.tangem.domain.earn.repository.EarnRepository
@ -38,6 +40,7 @@ internal class DefaultEarnRepository(
private val userWalletsListRepository: UserWalletsListRepository, private val userWalletsListRepository: UserWalletsListRepository,
private val earnNetworksStore: EarnNetworksStore, private val earnNetworksStore: EarnNetworksStore,
private val earnTopTokensStore: EarnTopTokensStore, private val earnTopTokensStore: EarnTopTokensStore,
private val earnFilterStore: EarnFilterStore,
private val earnErrorResolver: EarnErrorResolver, private val earnErrorResolver: EarnErrorResolver,
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
) : EarnRepository { ) : EarnRepository {
@ -123,6 +126,14 @@ internal class DefaultEarnRepository(
return earnTopTokensStore.get() return earnTopTokensStore.get()
} }
override fun observeEarnFilter(): Flow<EarnFilter> {
return earnFilterStore.get()
}
override suspend fun setEarnFilter(filter: EarnFilter) {
earnFilterStore.store(filter)
}
companion object { companion object {
internal const val FIRST_PAGE = 1 internal const val FIRST_PAGE = 1
private const val INITIAL_BATCH_KEY = 0 private const val INITIAL_BATCH_KEY = 0

View file

@ -0,0 +1,9 @@
package com.tangem.domain.earn.model
import kotlinx.serialization.Serializable
@Serializable
data class EarnFilter(
val earnFilterNetwork: EarnFilterNetwork,
val earnFilterType: EarnFilterType,
)

View file

@ -0,0 +1,27 @@
package com.tangem.domain.earn.model
import kotlinx.serialization.Serializable
@Serializable
sealed interface EarnFilterNetwork {
val isSelected: Boolean
@Serializable
data class AllNetworks(
override val isSelected: Boolean,
) : EarnFilterNetwork
@Serializable
data class MyNetworks(
override val isSelected: Boolean,
) : EarnFilterNetwork
@Serializable
data class Specific(
override val isSelected: Boolean,
val id: String,
val symbol: String,
val fullName: String,
) : EarnFilterNetwork
}

View file

@ -0,0 +1,7 @@
package com.tangem.domain.earn.model
enum class EarnFilterType {
ALL,
STAKING,
YIELD,
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.earn.repository package com.tangem.domain.earn.repository
import com.tangem.domain.earn.model.EarnFilter
import com.tangem.domain.earn.model.EarnTokensBatchFlow import com.tangem.domain.earn.model.EarnTokensBatchFlow
import com.tangem.domain.earn.model.EarnTokensBatchingContext import com.tangem.domain.earn.model.EarnTokensBatchingContext
import com.tangem.domain.models.earn.EarnNetworks import com.tangem.domain.models.earn.EarnNetworks
@ -23,4 +24,8 @@ interface EarnRepository {
suspend fun fetchTopEarnTokens(limit: Int) suspend fun fetchTopEarnTokens(limit: Int)
fun observeTopEarnTokens(): Flow<EarnTopToken?> fun observeTopEarnTokens(): Flow<EarnTopToken?>
fun observeEarnFilter(): Flow<EarnFilter>
suspend fun setEarnFilter(filter: EarnFilter)
} }

View file

@ -0,0 +1,12 @@
package com.tangem.domain.earn.usecase
import com.tangem.domain.earn.model.EarnFilter
import com.tangem.domain.earn.repository.EarnRepository
import kotlinx.coroutines.flow.Flow
class GetEarnFilterUseCase(private val repository: EarnRepository) {
operator fun invoke(): Flow<EarnFilter> {
return repository.observeEarnFilter()
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.earn.usecase
import com.tangem.domain.earn.model.EarnFilter
import com.tangem.domain.earn.repository.EarnRepository
class SetEarnFilterUseCase(private val repository: EarnRepository) {
suspend operator fun invoke(filter: EarnFilter) {
repository.setEarnFilter(filter)
}
}

View file

@ -19,7 +19,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.features.feed.components.feed.FeedPortfolioRoute import com.tangem.features.feed.components.feed.FeedBottomSheetRoute
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import com.tangem.features.feed.model.earn.EarnModel import com.tangem.features.feed.model.earn.EarnModel
import com.tangem.features.feed.ui.earn.EarnContent import com.tangem.features.feed.ui.earn.EarnContent
@ -35,7 +35,7 @@ internal class DefaultEarnComponent(
private val bottomSheetSlot = childSlot( private val bottomSheetSlot = childSlot(
source = earnModel.bottomSheetNavigation, source = earnModel.bottomSheetNavigation,
serializer = FeedPortfolioRoute.serializer(), serializer = null,
handleBackButton = false, handleBackButton = false,
childFactory = ::bottomSheetChild, childFactory = ::bottomSheetChild,
) )
@ -68,10 +68,10 @@ internal class DefaultEarnComponent(
} }
private fun bottomSheetChild( private fun bottomSheetChild(
config: FeedPortfolioRoute, config: FeedBottomSheetRoute,
componentContext: ComponentContext, componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) { ): ComposableBottomSheetComponent = when (config) {
is FeedPortfolioRoute.AddToPortfolio -> { is FeedBottomSheetRoute.AddToPortfolio -> {
addToPortfolioComponentFactory.create( addToPortfolioComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = AddToPortfolioPreselectedDataComponent.Params( params = AddToPortfolioPreselectedDataComponent.Params(
@ -80,6 +80,14 @@ internal class DefaultEarnComponent(
), ),
) )
} }
is FeedBottomSheetRoute.NetworkFilter -> EarnNetworkFilterComponent(
context = childByContext(componentContext),
params = config.params,
)
is FeedBottomSheetRoute.TypeFilter -> EarnTypeFilterComponent(
context = childByContext(componentContext),
params = config.params,
)
} }
@Serializable @Serializable

View file

@ -0,0 +1,43 @@
package com.tangem.features.feed.components.earn
import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.earn.model.EarnFilterNetwork
import com.tangem.features.feed.model.earn.filters.EarnNetworkFilterModel
import com.tangem.features.feed.ui.earn.components.EarnFilterByNetworkBottomSheet
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class EarnNetworkFilterComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: Params,
) : AppComponentContext by context, ComposableBottomSheetComponent {
private val model = getOrCreateModel<EarnNetworkFilterModel, Params>(params = params)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val state = model.state.collectAsStateWithLifecycle()
EarnFilterByNetworkBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = state.value,
),
)
}
data class Params(
val allFilters: List<EarnFilterNetwork>,
val onFilterSelected: (EarnFilterNetwork) -> Unit,
val onDismiss: () -> Unit,
)
}

View file

@ -0,0 +1,44 @@
package com.tangem.features.feed.components.earn
import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.earn.model.EarnFilterType
import com.tangem.features.feed.model.earn.filters.EarnTypeFilterModel
import com.tangem.features.feed.ui.earn.components.EarnFilterByTypeBottomSheet
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class EarnTypeFilterComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: Params,
) : AppComponentContext by context, ComposableBottomSheetComponent {
private val model = getOrCreateModel<EarnTypeFilterModel, Params>(params = params)
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
val state = model.state.collectAsStateWithLifecycle()
EarnFilterByTypeBottomSheet(
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = state.value,
),
)
}
data class Params(
val selectedFilter: EarnFilterType,
val onFilterSelected: (EarnFilterType) -> Unit,
val onDismiss: () -> Unit,
)
}

View file

@ -15,7 +15,9 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.decompose.EmptyComposableBottomSheetComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent.Params
import com.tangem.features.feed.model.feed.FeedComponentModel import com.tangem.features.feed.model.feed.FeedComponentModel
import com.tangem.features.feed.model.feed.FeedModelClickIntents import com.tangem.features.feed.model.feed.FeedModelClickIntents
import com.tangem.features.feed.ui.feed.FeedList import com.tangem.features.feed.ui.feed.FeedList
@ -31,7 +33,7 @@ internal class DefaultFeedComponent(
private val bottomSheetSlot = childSlot( private val bottomSheetSlot = childSlot(
source = feedComponentModel.bottomSheetNavigation, source = feedComponentModel.bottomSheetNavigation,
serializer = FeedPortfolioRoute.serializer(), serializer = null,
handleBackButton = false, handleBackButton = false,
childFactory = ::bottomSheetChild, childFactory = ::bottomSheetChild,
) )
@ -64,18 +66,20 @@ internal class DefaultFeedComponent(
} }
private fun bottomSheetChild( private fun bottomSheetChild(
config: FeedPortfolioRoute, config: FeedBottomSheetRoute,
componentContext: ComponentContext, componentContext: ComponentContext,
): ComposableBottomSheetComponent = when (config) { ): ComposableBottomSheetComponent = when (config) {
is FeedPortfolioRoute.AddToPortfolio -> { is FeedBottomSheetRoute.AddToPortfolio -> {
addToPortfolioComponentFactory.create( addToPortfolioComponentFactory.create(
context = childByContext(componentContext), context = childByContext(componentContext),
params = AddToPortfolioPreselectedDataComponent.Params( params = Params(
tokenToAdd = config.tokenToAdd, tokenToAdd = config.tokenToAdd,
callback = feedComponentModel.addToPortfolioCallback, callback = feedComponentModel.addToPortfolioCallback,
), ),
) )
} }
is FeedBottomSheetRoute.NetworkFilter -> EmptyComposableBottomSheetComponent
is FeedBottomSheetRoute.TypeFilter -> EmptyComposableBottomSheetComponent
} }
data class FeedParams(val feedClickIntents: FeedModelClickIntents) data class FeedParams(val feedClickIntents: FeedModelClickIntents)

View file

@ -0,0 +1,14 @@
package com.tangem.features.feed.components.feed
import com.tangem.features.feed.components.earn.EarnNetworkFilterComponent
import com.tangem.features.feed.components.earn.EarnTypeFilterComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
internal sealed interface FeedBottomSheetRoute {
data class AddToPortfolio(val tokenToAdd: AddToPortfolioPreselectedDataComponent.TokenToAdd) : FeedBottomSheetRoute
data class NetworkFilter(val params: EarnNetworkFilterComponent.Params) : FeedBottomSheetRoute
data class TypeFilter(val params: EarnTypeFilterComponent.Params) : FeedBottomSheetRoute
}

View file

@ -1,11 +0,0 @@
package com.tangem.features.feed.components.feed
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import kotlinx.serialization.Serializable
@Serializable
sealed interface FeedPortfolioRoute {
@Serializable
data class AddToPortfolio(val tokenToAdd: AddToPortfolioPreselectedDataComponent.TokenToAdd) : FeedPortfolioRoute
}

View file

@ -4,6 +4,8 @@ import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.features.feed.model.FeedEntryModel import com.tangem.features.feed.model.FeedEntryModel
import com.tangem.features.feed.model.earn.EarnModel import com.tangem.features.feed.model.earn.EarnModel
import com.tangem.features.feed.model.earn.filters.EarnNetworkFilterModel
import com.tangem.features.feed.model.earn.filters.EarnTypeFilterModel
import com.tangem.features.feed.model.feed.FeedComponentModel import com.tangem.features.feed.model.feed.FeedComponentModel
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
import com.tangem.features.feed.model.market.list.MarketsListModel import com.tangem.features.feed.model.market.list.MarketsListModel
@ -53,4 +55,14 @@ internal interface ModelModule {
@IntoMap @IntoMap
@ClassKey(FeedEntryModel::class) @ClassKey(FeedEntryModel::class)
fun provideFeedEntryModel(model: FeedEntryModel): Model fun provideFeedEntryModel(model: FeedEntryModel): Model
@Binds
@IntoMap
@ClassKey(EarnNetworkFilterModel::class)
fun provideEarnNetworkFilterModel(model: EarnNetworkFilterModel): Model
@Binds
@IntoMap
@ClassKey(EarnTypeFilterModel::class)
fun provideEarnTypeFilterModel(model: EarnTypeFilterModel): Model
} }

View file

@ -5,14 +5,14 @@ import arrow.core.Either
import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.R
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.domain.earn.model.EarnFilter
import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.earn.model.EarnFilterNetwork
import com.tangem.domain.earn.model.EarnFilterType
import com.tangem.domain.earn.usecase.* import com.tangem.domain.earn.usecase.*
import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
@ -20,17 +20,26 @@ import com.tangem.domain.models.earn.EarnNetworks
import com.tangem.domain.models.earn.EarnTokenWithCurrency import com.tangem.domain.models.earn.EarnTokenWithCurrency
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.feed.components.earn.DefaultEarnComponent import com.tangem.features.feed.components.earn.DefaultEarnComponent
import com.tangem.features.feed.components.feed.FeedPortfolioRoute import com.tangem.features.feed.components.earn.EarnNetworkFilterComponent
import com.tangem.features.feed.components.earn.EarnTypeFilterComponent
import com.tangem.features.feed.components.feed.FeedBottomSheetRoute
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import com.tangem.features.feed.model.earn.filters.state.EarnFilterNetworkConverter
import com.tangem.features.feed.model.earn.filters.state.EarnFilterNetworkUMConverter
import com.tangem.features.feed.model.earn.filters.state.EarnFilterTypeConverter
import com.tangem.features.feed.model.earn.filters.state.EarnFilterTypeUMConverter
import com.tangem.features.feed.model.earn.state.EarnStateController import com.tangem.features.feed.model.earn.state.EarnStateController
import com.tangem.features.feed.model.earn.state.converter.EarnNetworksConverter import com.tangem.features.feed.model.earn.state.transformers.EarnFilterSelectedStateTransformer
import com.tangem.features.feed.model.earn.state.transformers.* import com.tangem.features.feed.model.earn.state.transformers.UpdateBestOpportunitiesStateTransformer
import com.tangem.features.feed.model.earn.state.transformers.UpdateEarnUMInitialStateTransformer
import com.tangem.features.feed.model.earn.state.transformers.UpdateMostlyUsedStateTransformer
import com.tangem.features.feed.model.earn.statemanager.EarnListBatchFlowManager import com.tangem.features.feed.model.earn.statemanager.EarnListBatchFlowManager
import com.tangem.features.feed.model.earn.statemanager.EarnListStateManager import com.tangem.features.feed.model.earn.statemanager.EarnListStateManager
import com.tangem.features.feed.ui.earn.state.* import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.features.feed.ui.earn.state.EarnUM
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -46,13 +55,14 @@ internal class EarnModel @Inject constructor(
private val getEarnTokensBatchFlowUseCase: GetEarnTokensBatchFlowUseCase, private val getEarnTokensBatchFlowUseCase: GetEarnTokensBatchFlowUseCase,
private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase, private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase,
private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase, private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase,
private val getEarnFilterUseCase: GetEarnFilterUseCase,
private val setEarnFilterUseCase: SetEarnFilterUseCase,
private val appRouter: AppRouter, private val appRouter: AppRouter,
private val stateController: EarnStateController, private val stateController: EarnStateController,
) : Model() { ) : Model() {
private val params = paramsContainer.require<DefaultEarnComponent.Params>() private val params = paramsContainer.require<DefaultEarnComponent.Params>()
private val earnNetworks = MutableStateFlow<EarnNetworks>(Either.Right(emptyList())) private val earnNetworks = MutableStateFlow<EarnNetworks>(Either.Right(emptyList()))
private val earnNetworksConverter: EarnNetworksConverter by lazy { EarnNetworksConverter() }
private val earnListConfigProvider = Provider { private val earnListConfigProvider = Provider {
createEarnTokensListConfig( createEarnTokensListConfig(
selectedTypeFilter = stateController.value.selectedTypeFilter, selectedTypeFilter = stateController.value.selectedTypeFilter,
@ -61,17 +71,15 @@ internal class EarnModel @Inject constructor(
) )
} }
private val batchFlowManager by lazy { private val batchFlowManager = EarnListBatchFlowManager(
EarnListBatchFlowManager( getEarnTokensBatchFlowUseCase = getEarnTokensBatchFlowUseCase,
getEarnTokensBatchFlowUseCase = getEarnTokensBatchFlowUseCase, configProvider = earnListConfigProvider,
configProvider = earnListConfigProvider, onItemClick = ::onEarnTokenClick,
onItemClick = ::onEarnTokenClick, modelScope = modelScope,
modelScope = modelScope, dispatchers = dispatchers,
dispatchers = dispatchers, )
)
}
val bottomSheetNavigation: SlotNavigation<FeedPortfolioRoute> = SlotNavigation() val bottomSheetNavigation: SlotNavigation<FeedBottomSheetRoute> = SlotNavigation()
val addToPortfolioCallback = object : AddToPortfolioPreselectedDataComponent.Callback { val addToPortfolioCallback = object : AddToPortfolioPreselectedDataComponent.Callback {
override fun onDismiss() = bottomSheetNavigation.dismiss() override fun onDismiss() = bottomSheetNavigation.dismiss()
@ -93,6 +101,7 @@ internal class EarnModel @Inject constructor(
updateInitialState() updateInitialState()
fetchEarnNetworks() fetchEarnNetworks()
fetchTopEarnTokens() fetchTopEarnTokens()
subscribeOnStoredFilters()
subscribeOnNetworks() subscribeOnNetworks()
subscribeOnBatchFlow() subscribeOnBatchFlow()
subscribeToMostlyUsed() subscribeToMostlyUsed()
@ -117,9 +126,7 @@ internal class EarnModel @Inject constructor(
) )
}.onEach { bestOpportunitiesState -> }.onEach { bestOpportunitiesState ->
stateController.update(UpdateBestOpportunitiesStateTransformer(bestOpportunitiesState)) stateController.update(UpdateBestOpportunitiesStateTransformer(bestOpportunitiesState))
} }.launchIn(modelScope)
.onStart { batchFlowManager.reload() }
.launchIn(modelScope)
} }
private fun subscribeToMostlyUsed() { private fun subscribeToMostlyUsed() {
@ -142,6 +149,23 @@ internal class EarnModel @Inject constructor(
} }
} }
private fun subscribeOnStoredFilters() {
modelScope.launch(dispatchers.default) {
getEarnFilterUseCase()
.collect { filter ->
val typeFilterUM = EarnFilterTypeConverter().convert(filter.earnFilterType)
val networkFilterUM = EarnFilterNetworkConverter().convert(filter.earnFilterNetwork)
stateController.update(
EarnFilterSelectedStateTransformer(
filterType = typeFilterUM,
filterNetwork = networkFilterUM,
),
)
batchFlowManager.reload()
}
}
}
private fun fetchTopEarnTokens() { private fun fetchTopEarnTokens() {
modelScope.launch(dispatchers.default) { modelScope.launch(dispatchers.default) {
fetchTopEarnTokensUseCase() fetchTopEarnTokensUseCase()
@ -154,142 +178,73 @@ internal class EarnModel @Inject constructor(
} }
} }
private fun createTypeFilterBottomSheetConfig(
selectedOption: EarnFilterTypeUM,
isShown: Boolean = false,
): TangemBottomSheetConfig {
return TangemBottomSheetConfig(
isShown = isShown,
onDismissRequest = ::hideTypeFilterBottomSheet,
content = EarnFilterByTypeBottomSheetContentUM(
selectedOption = selectedOption,
onOptionClick = ::onTypeFilterOptionSelected,
),
)
}
private fun createNetworkFilterBottomSheetConfig(
selectedNetwork: EarnFilterNetworkUM,
isShown: Boolean = false,
): TangemBottomSheetConfig {
val filters = buildList {
add(
EarnFilterNetworkUM.AllNetworks(
text = TextReference.Res(R.string.earn_filter_all_networks),
isSelected = false,
),
)
add(
EarnFilterNetworkUM.MyNetworks(
text = TextReference.Res(R.string.earn_filter_my_networks),
isSelected = false,
),
)
earnNetworks.value.onRight { networks ->
addAll(networks.map(earnNetworksConverter::convert))
}
}
val updatedNetworks = filters.map { filter ->
val shouldBeSelected = when (filter) {
is EarnFilterNetworkUM.AllNetworks -> selectedNetwork is EarnFilterNetworkUM.AllNetworks
is EarnFilterNetworkUM.MyNetworks -> selectedNetwork is EarnFilterNetworkUM.MyNetworks
is EarnFilterNetworkUM.Network ->
selectedNetwork is EarnFilterNetworkUM.Network && filter.id == selectedNetwork.id
}
if (filter.isSelected != shouldBeSelected) {
when (filter) {
is EarnFilterNetworkUM.AllNetworks -> filter.copy(isSelected = shouldBeSelected)
is EarnFilterNetworkUM.MyNetworks -> filter.copy(isSelected = shouldBeSelected)
is EarnFilterNetworkUM.Network -> filter.copy(isSelected = shouldBeSelected)
}
} else {
filter
}
}
return TangemBottomSheetConfig(
isShown = isShown,
onDismissRequest = ::hideNetworkFilterBottomSheet,
content = EarnFilterByNetworkBottomSheetContentUM(
selectedNetwork = selectedNetwork,
networks = persistentListOf(*updatedNetworks.toTypedArray()),
onOptionClick = ::onNetworkFilterOptionSelected,
),
)
}
/* start of clicks area */ /* start of clicks area */
private fun onTypeFilterClick() { private fun onTypeFilterClick() {
stateController.update( val currentState = state.value
EarnTypeFilterStateTransformer( bottomSheetNavigation.activate(
shouldShow = true, FeedBottomSheetRoute.TypeFilter(
filterByTypeBottomSheetConfig = ::createTypeFilterBottomSheetConfig, params = EarnTypeFilterComponent.Params(
), selectedFilter = EarnFilterTypeUMConverter().convert(currentState.selectedTypeFilter),
) onFilterSelected = ::onTypeFilterOptionSelected,
} onDismiss = { bottomSheetNavigation.dismiss() },
),
private fun hideTypeFilterBottomSheet() {
stateController.update(
EarnTypeFilterStateTransformer(
shouldShow = false,
filterByTypeBottomSheetConfig = ::createTypeFilterBottomSheetConfig,
), ),
) )
} }
private fun onNetworkFilterClick() { private fun onNetworkFilterClick() {
stateController.update( bottomSheetNavigation.activate(
EarnNetworkFilterStateTransformer( FeedBottomSheetRoute.NetworkFilter(
shouldShow = true, params = EarnNetworkFilterComponent.Params(
filterByNetworkBottomSheetConfig = ::createNetworkFilterBottomSheetConfig, allFilters = createNetworkFilters(),
onFilterSelected = ::onNetworkFilterOptionSelected,
onDismiss = { bottomSheetNavigation.dismiss() },
),
), ),
) )
} }
private fun hideNetworkFilterBottomSheet() { private fun createNetworkFilters(): List<EarnFilterNetwork> {
stateController.update( val selectedFilter = state.value.selectedNetworkFilter
EarnNetworkFilterStateTransformer( return buildList {
shouldShow = false, add(
filterByNetworkBottomSheetConfig = ::createNetworkFilterBottomSheetConfig, EarnFilterNetwork.AllNetworks(
), isSelected = selectedFilter is EarnFilterNetworkUM.AllNetworks,
) ),
} )
add(
private fun onTypeFilterOptionSelected(type: EarnFilterTypeUM) { EarnFilterNetwork.MyNetworks(
stateController.update( isSelected = selectedFilter is EarnFilterNetworkUM.MyNetworks,
EarnTypeFilterSelectedStateTransformer( ),
filter = type, )
filterByTypeBottomSheetConfig = ::createTypeFilterBottomSheetConfig, earnNetworks.value.onRight { networks ->
), networks.mapTo(this) { network ->
) EarnFilterNetwork.Specific(
batchFlowManager.reload() id = network.networkId,
} isSelected = selectedFilter is EarnFilterNetworkUM.Network &&
selectedFilter.id == network.networkId,
private fun onNetworkFilterOptionSelected(network: EarnFilterNetworkUM) { symbol = network.symbol,
stateController.update( fullName = network.fullName,
EarnNetworkFilterSelectedStateTransformer( )
filter = network, }
filterByNetworkBottomSheetConfig = ::createNetworkFilterBottomSheetConfig, }
), }
)
batchFlowManager.reload()
} }
private fun onClearFiltersClick() { private fun onClearFiltersClick() {
stateController.update( modelScope.launch(dispatchers.default) {
ClearEarnUMFilterStateTransformer( setEarnFilterUseCase(
filterByTypeBottomSheetConfig = ::createTypeFilterBottomSheetConfig, EarnFilter(
filterByNetworkBottomSheetConfig = ::createNetworkFilterBottomSheetConfig, earnFilterNetwork = EarnFilterNetwork.AllNetworks(isSelected = true),
), earnFilterType = EarnFilterType.ALL,
) ),
batchFlowManager.reload() )
}
} }
private fun onEarnTokenClick(earnTokenWithCurrency: EarnTokenWithCurrency) { private fun onEarnTokenClick(earnTokenWithCurrency: EarnTokenWithCurrency) {
bottomSheetNavigation.activate( bottomSheetNavigation.activate(
FeedPortfolioRoute.AddToPortfolio( FeedBottomSheetRoute.AddToPortfolio(
tokenToAdd = AddToPortfolioPreselectedDataComponent.TokenToAdd( tokenToAdd = AddToPortfolioPreselectedDataComponent.TokenToAdd(
network = TokenMarketInfo.Network( network = TokenMarketInfo.Network(
networkId = earnTokenWithCurrency.earnToken.networkId, networkId = earnTokenWithCurrency.earnToken.networkId,
@ -304,6 +259,30 @@ internal class EarnModel @Inject constructor(
), ),
) )
} }
private fun onTypeFilterOptionSelected(type: EarnFilterType) {
modelScope.launch(dispatchers.default) {
setEarnFilterUseCase(
EarnFilter(
earnFilterNetwork = EarnFilterNetworkUMConverter().convert(state.value.selectedNetworkFilter),
earnFilterType = type,
),
)
bottomSheetNavigation.dismiss()
}
}
private fun onNetworkFilterOptionSelected(filter: EarnFilterNetwork) {
modelScope.launch(dispatchers.default) {
setEarnFilterUseCase(
EarnFilter(
earnFilterNetwork = filter,
earnFilterType = EarnFilterTypeUMConverter().convert(state.value.selectedTypeFilter),
),
)
}
bottomSheetNavigation.dismiss()
}
/* end of clicks area */ /* end of clicks area */
private fun updateInitialState() { private fun updateInitialState() {
@ -311,10 +290,6 @@ internal class EarnModel @Inject constructor(
UpdateEarnUMInitialStateTransformer( UpdateEarnUMInitialStateTransformer(
onBackClick = params.onBackClick, onBackClick = params.onBackClick,
onNetworkFilterClick = ::onNetworkFilterClick, onNetworkFilterClick = ::onNetworkFilterClick,
filterByTypeBottomSheetConfig = createTypeFilterBottomSheetConfig(state.value.selectedTypeFilter),
filterByNetworkBottomSheetConfig = createNetworkFilterBottomSheetConfig(
selectedNetwork = state.value.selectedNetworkFilter,
),
onTypeFilterClick = ::onTypeFilterClick, onTypeFilterClick = ::onTypeFilterClick,
), ),
) )

View file

@ -0,0 +1,37 @@
package com.tangem.features.feed.model.earn.filters
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.feed.components.earn.EarnNetworkFilterComponent
import com.tangem.features.feed.model.earn.filters.state.EarnFilterNetworkUMConverter
import com.tangem.features.feed.model.earn.filters.state.EarnFilterNetworkConverter
import com.tangem.features.feed.ui.earn.state.EarnFilterByNetworkBottomSheetContentUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@Stable
@ModelScoped
internal class EarnNetworkFilterModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
) : Model() {
private val params = paramsContainer.require<EarnNetworkFilterComponent.Params>()
private val _state = MutableStateFlow(configureInitialState())
val state = _state.asStateFlow()
private fun configureInitialState(): EarnFilterByNetworkBottomSheetContentUM {
return EarnFilterByNetworkBottomSheetContentUM(
networks = params.allFilters
.map { EarnFilterNetworkConverter().convert(it) }
.toPersistentList(),
onOptionClick = { filterUM -> params.onFilterSelected(EarnFilterNetworkUMConverter().convert(filterUM)) },
)
}
}

View file

@ -0,0 +1,36 @@
package com.tangem.features.feed.model.earn.filters
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.feed.components.earn.EarnTypeFilterComponent
import com.tangem.features.feed.model.earn.filters.state.EarnFilterTypeConverter
import com.tangem.features.feed.model.earn.filters.state.EarnFilterTypeUMConverter
import com.tangem.features.feed.ui.earn.state.EarnFilterByTypeBottomSheetContentUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@Stable
@ModelScoped
internal class EarnTypeFilterModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
) : Model() {
private val params = paramsContainer.require<EarnTypeFilterComponent.Params>()
private val _state = MutableStateFlow(configureInitialState())
val state = _state.asStateFlow()
private fun configureInitialState(): EarnFilterByTypeBottomSheetContentUM {
return EarnFilterByTypeBottomSheetContentUM(
selectedOption = EarnFilterTypeConverter().convert(params.selectedFilter),
onOptionClick = { filterTypeUM ->
params.onFilterSelected(EarnFilterTypeUMConverter().convert(filterTypeUM))
},
)
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.features.feed.model.earn.filters.state
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.domain.earn.model.EarnFilterNetwork
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.utils.converter.Converter
internal class EarnFilterNetworkConverter : Converter<EarnFilterNetwork, EarnFilterNetworkUM> {
override fun convert(value: EarnFilterNetwork): EarnFilterNetworkUM {
return when (value) {
is EarnFilterNetwork.AllNetworks -> {
EarnFilterNetworkUM.AllNetworks(isSelected = value.isSelected)
}
is EarnFilterNetwork.MyNetworks -> {
EarnFilterNetworkUM.MyNetworks(isSelected = value.isSelected)
}
is EarnFilterNetwork.Specific -> {
EarnFilterNetworkUM.Network(
id = value.id,
text = value.fullName,
symbol = value.symbol,
iconRes = getActiveIconRes(Blockchain.fromNetworkId(value.id)?.id.orEmpty()),
isSelected = value.isSelected,
)
}
}
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.features.feed.model.earn.filters.state
import com.tangem.domain.earn.model.EarnFilterNetwork
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.utils.converter.Converter
internal class EarnFilterNetworkUMConverter : Converter<EarnFilterNetworkUM, EarnFilterNetwork> {
override fun convert(value: EarnFilterNetworkUM): EarnFilterNetwork {
return when (value) {
is EarnFilterNetworkUM.AllNetworks -> EarnFilterNetwork.AllNetworks(isSelected = value.isSelected)
is EarnFilterNetworkUM.MyNetworks -> EarnFilterNetwork.MyNetworks(isSelected = value.isSelected)
is EarnFilterNetworkUM.Network -> EarnFilterNetwork.Specific(
isSelected = value.isSelected,
id = value.id,
symbol = value.symbol,
fullName = value.text,
)
}
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.features.feed.model.earn.filters.state
import com.tangem.domain.earn.model.EarnFilterType
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.utils.converter.Converter
internal class EarnFilterTypeConverter : Converter<EarnFilterType, EarnFilterTypeUM> {
override fun convert(value: EarnFilterType): EarnFilterTypeUM {
return when (value) {
EarnFilterType.ALL -> EarnFilterTypeUM.All
EarnFilterType.STAKING -> EarnFilterTypeUM.Staking
EarnFilterType.YIELD -> EarnFilterTypeUM.YieldMode
}
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.features.feed.model.earn.filters.state
import com.tangem.domain.earn.model.EarnFilterType
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.utils.converter.Converter
internal class EarnFilterTypeUMConverter : Converter<EarnFilterTypeUM, EarnFilterType> {
override fun convert(value: EarnFilterTypeUM): EarnFilterType {
return when (value) {
EarnFilterTypeUM.All -> EarnFilterType.ALL
EarnFilterTypeUM.Staking -> EarnFilterType.STAKING
EarnFilterTypeUM.YieldMode -> EarnFilterType.YIELD
}
}
}

View file

@ -1,9 +1,6 @@
package com.tangem.features.feed.model.earn.state package com.tangem.features.feed.model.earn.state
import com.tangem.common.R
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.feed.model.earn.state.transformers.EarnUMTransformer import com.tangem.features.feed.model.earn.state.transformers.EarnUMTransformer
import com.tangem.features.feed.ui.earn.state.* import com.tangem.features.feed.ui.earn.state.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -34,12 +31,7 @@ internal class EarnStateController @Inject constructor() {
mostlyUsed = EarnListUM.Loading, mostlyUsed = EarnListUM.Loading,
bestOpportunities = EarnBestOpportunitiesUM.Loading, bestOpportunities = EarnBestOpportunitiesUM.Loading,
selectedTypeFilter = EarnFilterTypeUM.All, selectedTypeFilter = EarnFilterTypeUM.All,
selectedNetworkFilter = EarnFilterNetworkUM.AllNetworks( selectedNetworkFilter = EarnFilterNetworkUM.AllNetworks(isSelected = true),
text = TextReference.Res(R.string.earn_filter_all_networks),
isSelected = true,
),
filterByTypeBottomSheet = TangemBottomSheetConfig.Empty,
filterByNetworkBottomSheet = TangemBottomSheetConfig.Empty,
onBackClick = {}, onBackClick = {},
onNetworkFilterClick = {}, onNetworkFilterClick = {},
onTypeFilterClick = {}, onTypeFilterClick = {},

View file

@ -1,22 +0,0 @@
package com.tangem.features.feed.model.earn.state.converter
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.domain.models.earn.EarnNetwork
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.utils.converter.Converter
internal class EarnNetworksConverter : Converter<EarnNetwork, EarnFilterNetworkUM.Network> {
override fun convert(value: EarnNetwork): EarnFilterNetworkUM.Network {
return EarnFilterNetworkUM.Network(
id = value.networkId,
text = TextReference.Str(value.fullName),
symbol = TextReference.Str(value.symbol),
iconRes = getActiveIconRes(Blockchain.fromNetworkId(value.networkId)?.id.orEmpty()),
isSelected = false,
)
}
}

View file

@ -1,28 +0,0 @@
package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.common.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.features.feed.ui.earn.state.EarnUM
internal class ClearEarnUMFilterStateTransformer(
private val filterByTypeBottomSheetConfig: (EarnFilterTypeUM, Boolean) -> TangemBottomSheetConfig,
private val filterByNetworkBottomSheetConfig: (EarnFilterNetworkUM, Boolean) -> TangemBottomSheetConfig,
) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM {
val defaultNetworksFilter = EarnFilterNetworkUM.AllNetworks(
text = TextReference.Res(R.string.earn_filter_all_networks),
isSelected = true,
)
return prevState.copy(
selectedTypeFilter = EarnFilterTypeUM.All,
selectedNetworkFilter = defaultNetworksFilter,
filterByTypeBottomSheet = filterByTypeBottomSheetConfig(EarnFilterTypeUM.All, false),
filterByNetworkBottomSheet = filterByNetworkBottomSheetConfig(defaultNetworksFilter, false),
)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.features.feed.ui.earn.state.EarnUM
internal class EarnFilterSelectedStateTransformer(
private val filterType: EarnFilterTypeUM,
private val filterNetwork: EarnFilterNetworkUM,
) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM {
return prevState.copy(
selectedTypeFilter = filterType,
selectedNetworkFilter = filterNetwork,
)
}
}

View file

@ -1,18 +1,13 @@
package com.tangem.features.feed.model.earn.state.transformers package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnUM import com.tangem.features.feed.ui.earn.state.EarnUM
internal class EarnNetworkFilterSelectedStateTransformer( internal class EarnNetworkFilterSelectedStateTransformer(
private val filter: EarnFilterNetworkUM, private val filter: EarnFilterNetworkUM,
private val filterByNetworkBottomSheetConfig: (EarnFilterNetworkUM, Boolean) -> TangemBottomSheetConfig,
) : EarnUMTransformer { ) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM { override fun transform(prevState: EarnUM): EarnUM {
return prevState.copy( return prevState.copy(selectedNetworkFilter = filter)
selectedNetworkFilter = filter,
filterByNetworkBottomSheet = filterByNetworkBottomSheetConfig(filter, false),
)
} }
} }

View file

@ -1,21 +0,0 @@
package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.features.feed.ui.earn.state.EarnFilterNetworkUM
import com.tangem.features.feed.ui.earn.state.EarnUM
internal class EarnNetworkFilterStateTransformer(
private val shouldShow: Boolean,
private val filterByNetworkBottomSheetConfig: (EarnFilterNetworkUM, Boolean) -> TangemBottomSheetConfig,
) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM {
return if (shouldShow) {
prevState.copy(
filterByNetworkBottomSheet = filterByNetworkBottomSheetConfig(prevState.selectedNetworkFilter, true),
)
} else {
prevState.copy(filterByNetworkBottomSheet = prevState.filterByNetworkBottomSheet.copy(isShown = false))
}
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.features.feed.ui.earn.state.EarnUM
internal class EarnTypeFilterSelectedStateTransformer(
private val filter: EarnFilterTypeUM,
private val filterByTypeBottomSheetConfig: (EarnFilterTypeUM, Boolean) -> TangemBottomSheetConfig,
) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM {
return prevState.copy(
selectedTypeFilter = filter,
filterByTypeBottomSheet = filterByTypeBottomSheetConfig(filter, false),
)
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.features.feed.ui.earn.state.EarnFilterTypeUM
import com.tangem.features.feed.ui.earn.state.EarnUM
internal class EarnTypeFilterStateTransformer(
private val shouldShow: Boolean,
private val filterByTypeBottomSheetConfig: (EarnFilterTypeUM, Boolean) -> TangemBottomSheetConfig,
) : EarnUMTransformer {
override fun transform(prevState: EarnUM): EarnUM {
return if (shouldShow) {
prevState.copy(
filterByTypeBottomSheet = filterByTypeBottomSheetConfig(prevState.selectedTypeFilter, true),
)
} else {
prevState.copy(filterByTypeBottomSheet = prevState.filterByTypeBottomSheet.copy(isShown = false))
}
}
}

View file

@ -1,12 +1,9 @@
package com.tangem.features.feed.model.earn.state.transformers package com.tangem.features.feed.model.earn.state.transformers
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.features.feed.ui.earn.state.EarnUM import com.tangem.features.feed.ui.earn.state.EarnUM
internal class UpdateEarnUMInitialStateTransformer( internal class UpdateEarnUMInitialStateTransformer(
private val onBackClick: () -> Unit, private val onBackClick: () -> Unit,
private val filterByTypeBottomSheetConfig: TangemBottomSheetConfig,
private val filterByNetworkBottomSheetConfig: TangemBottomSheetConfig,
private val onNetworkFilterClick: () -> Unit, private val onNetworkFilterClick: () -> Unit,
private val onTypeFilterClick: () -> Unit, private val onTypeFilterClick: () -> Unit,
) : EarnUMTransformer { ) : EarnUMTransformer {
@ -14,8 +11,6 @@ internal class UpdateEarnUMInitialStateTransformer(
override fun transform(prevState: EarnUM): EarnUM { override fun transform(prevState: EarnUM): EarnUM {
return prevState.copy( return prevState.copy(
onBackClick = onBackClick, onBackClick = onBackClick,
filterByNetworkBottomSheet = filterByNetworkBottomSheetConfig,
filterByTypeBottomSheet = filterByTypeBottomSheetConfig,
onNetworkFilterClick = onNetworkFilterClick, onNetworkFilterClick = onNetworkFilterClick,
onTypeFilterClick = onTypeFilterClick, onTypeFilterClick = onTypeFilterClick,
) )

View file

@ -29,7 +29,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.news.usecase.FetchTrendingNewsUseCase import com.tangem.domain.news.usecase.FetchTrendingNewsUseCase
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
import com.tangem.features.feed.components.feed.DefaultFeedComponent import com.tangem.features.feed.components.feed.DefaultFeedComponent
import com.tangem.features.feed.components.feed.FeedPortfolioRoute import com.tangem.features.feed.components.feed.FeedBottomSheetRoute
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.feed.impl.R import com.tangem.features.feed.impl.R
@ -89,7 +89,7 @@ internal class FeedComponentModel @Inject constructor(
dispatchers = dispatchers, dispatchers = dispatchers,
) )
val bottomSheetNavigation: SlotNavigation<FeedPortfolioRoute> = SlotNavigation() val bottomSheetNavigation: SlotNavigation<FeedBottomSheetRoute> = SlotNavigation()
val addToPortfolioCallback = object : AddToPortfolioPreselectedDataComponent.Callback { val addToPortfolioCallback = object : AddToPortfolioPreselectedDataComponent.Callback {
override fun onDismiss() = bottomSheetNavigation.dismiss() override fun onDismiss() = bottomSheetNavigation.dismiss()
@ -398,7 +398,7 @@ internal class FeedComponentModel @Inject constructor(
private fun handleEarnTokenClick(earnTokenWithCurrency: EarnTokenWithCurrency) { private fun handleEarnTokenClick(earnTokenWithCurrency: EarnTokenWithCurrency) {
bottomSheetNavigation.activate( bottomSheetNavigation.activate(
FeedPortfolioRoute.AddToPortfolio( FeedBottomSheetRoute.AddToPortfolio(
tokenToAdd = AddToPortfolioPreselectedDataComponent.TokenToAdd( tokenToAdd = AddToPortfolioPreselectedDataComponent.TokenToAdd(
network = TokenMarketInfo.Network( network = TokenMarketInfo.Network(
networkId = earnTokenWithCurrency.earnToken.networkId, networkId = earnTokenWithCurrency.earnToken.networkId,

View file

@ -24,7 +24,6 @@ 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.R
import com.tangem.core.ui.components.* import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.buttons.SecondarySmallButton import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
@ -37,7 +36,9 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.feed.ui.earn.components.* import com.tangem.features.feed.ui.earn.components.EarnItemPlaceholder
import com.tangem.features.feed.ui.earn.components.EarnListItem
import com.tangem.features.feed.ui.earn.components.MostlyUsedPlaceholder
import com.tangem.features.feed.ui.earn.state.* import com.tangem.features.feed.ui.earn.state.*
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -50,9 +51,6 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() } val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
val listState = rememberLazyListState() val listState = rememberLazyListState()
EarnFilterByTypeBottomSheet(config = state.filterByTypeBottomSheet)
EarnFilterByNetworkBottomSheet(config = state.filterByNetworkBottomSheet)
if (state.bestOpportunities is EarnBestOpportunitiesUM.Content) { if (state.bestOpportunities is EarnBestOpportunitiesUM.Content) {
PaginationHandler( PaginationHandler(
listState = listState, listState = listState,
@ -89,7 +87,11 @@ internal fun EarnContent(state: EarnUM, modifier: Modifier = Modifier) {
SpacerH(12.dp) SpacerH(12.dp)
BestOpportunitiesFilters( BestOpportunitiesFilters(
state = state.bestOpportunities, state = state.bestOpportunities,
selectedNetworkFilterText = state.selectedNetworkFilterText, selectedNetworkFilterText = when (state.selectedNetworkFilter) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(state.selectedNetworkFilter.text)
},
selectedTypeFilterText = state.selectedTypeFilterText, selectedTypeFilterText = state.selectedTypeFilterText,
onNetworkFilterClick = state.onNetworkFilterClick, onNetworkFilterClick = state.onNetworkFilterClick,
onTypeFilterClick = state.onTypeFilterClick, onTypeFilterClick = state.onTypeFilterClick,
@ -573,12 +575,7 @@ private fun previewEarnUM(
mostlyUsed = mostlyUsed, mostlyUsed = mostlyUsed,
bestOpportunities = bestOpportunities, bestOpportunities = bestOpportunities,
selectedTypeFilter = EarnFilterTypeUM.All, selectedTypeFilter = EarnFilterTypeUM.All,
selectedNetworkFilter = EarnFilterNetworkUM.AllNetworks( selectedNetworkFilter = EarnFilterNetworkUM.AllNetworks(isSelected = true),
text = resourceReference(R.string.earn_filter_all_networks),
isSelected = true,
),
filterByTypeBottomSheet = TangemBottomSheetConfig.Empty,
filterByNetworkBottomSheet = TangemBottomSheetConfig.Empty,
onBackClick = {}, onBackClick = {},
onNetworkFilterClick = {}, onNetworkFilterClick = {},
onTypeFilterClick = {}, onTypeFilterClick = {},

View file

@ -28,9 +28,8 @@ import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.components.rows.RowContentContainer import com.tangem.core.ui.components.rows.RowContentContainer
import com.tangem.core.ui.components.rows.RowText import com.tangem.core.ui.components.rows.RowText
import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
@ -106,7 +105,11 @@ private fun LazyListScope.allMyNetworksList(
showDivider = index != allMyNetworks.lastIndex, showDivider = index != allMyNetworks.lastIndex,
) { ) {
InputRowChecked( InputRowChecked(
text = item.text, text = when (item) {
is EarnFilterNetworkUM.AllNetworks -> TextReference.Res(R.string.earn_filter_all_networks)
is EarnFilterNetworkUM.MyNetworks -> TextReference.Res(R.string.earn_filter_my_networks)
is EarnFilterNetworkUM.Network -> TextReference.Str(item.text)
},
checked = item.isSelected, checked = item.isSelected,
) )
} }
@ -170,13 +173,13 @@ private fun LazyListScope.specificNetworksList(
Image( Image(
modifier = Modifier.size(22.dp), modifier = Modifier.size(22.dp),
imageVector = ImageVector.vectorResource(item.iconRes), imageVector = ImageVector.vectorResource(item.iconRes),
contentDescription = item.symbol.resolveReference(), contentDescription = item.symbol,
) )
}, },
text = { text = {
RowText( RowText(
mainText = item.text.resolveReference(), mainText = item.text,
secondText = item.symbol.resolveReference(), secondText = item.symbol,
accentMainText = true, accentMainText = true,
accentSecondText = false, accentSecondText = false,
) )
@ -208,30 +211,20 @@ private fun Preview() {
isShown = true, isShown = true,
onDismissRequest = {}, onDismissRequest = {},
content = EarnFilterByNetworkBottomSheetContentUM( content = EarnFilterByNetworkBottomSheetContentUM(
selectedNetwork = EarnFilterNetworkUM.AllNetworks(
text = resourceReference(R.string.earn_filter_all_networks),
isSelected = true,
),
networks = persistentListOf( networks = persistentListOf(
EarnFilterNetworkUM.AllNetworks( EarnFilterNetworkUM.AllNetworks(isSelected = true),
text = resourceReference(R.string.earn_filter_all_networks), EarnFilterNetworkUM.MyNetworks(isSelected = false),
isSelected = true,
),
EarnFilterNetworkUM.MyNetworks(
text = stringReference("My network"),
isSelected = false,
),
EarnFilterNetworkUM.Network( EarnFilterNetworkUM.Network(
id = "ethereum", id = "ethereum",
text = stringReference("Ethereum"), text = "Ethereum",
symbol = stringReference("ETH"), symbol = "ETH",
iconRes = R.drawable.img_btc_22, iconRes = R.drawable.img_btc_22,
isSelected = false, isSelected = false,
), ),
EarnFilterNetworkUM.Network( EarnFilterNetworkUM.Network(
id = "polygon", id = "polygon",
text = stringReference("Polygon"), text = "Polygon",
symbol = stringReference("MATIC"), symbol = "MATIC",
iconRes = R.drawable.img_btc_22, iconRes = R.drawable.img_btc_22,
isSelected = false, isSelected = false,
), ),

View file

@ -4,7 +4,6 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
internal data class EarnFilterByNetworkBottomSheetContentUM( internal data class EarnFilterByNetworkBottomSheetContentUM(
val selectedNetwork: EarnFilterNetworkUM,
val networks: ImmutableList<EarnFilterNetworkUM>, val networks: ImmutableList<EarnFilterNetworkUM>,
val onOptionClick: (EarnFilterNetworkUM) -> Unit, val onOptionClick: (EarnFilterNetworkUM) -> Unit,
) : TangemBottomSheetConfigContent ) : TangemBottomSheetConfigContent

View file

@ -1,29 +1,25 @@
package com.tangem.features.feed.ui.earn.state package com.tangem.features.feed.ui.earn.state
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable @Immutable
internal sealed class EarnFilterNetworkUM { internal sealed class EarnFilterNetworkUM {
abstract val text: TextReference
abstract val isSelected: Boolean abstract val isSelected: Boolean
data class AllNetworks( data class AllNetworks(
override val text: TextReference,
override val isSelected: Boolean, override val isSelected: Boolean,
) : EarnFilterNetworkUM() ) : EarnFilterNetworkUM()
data class MyNetworks( data class MyNetworks(
override val text: TextReference,
override val isSelected: Boolean, override val isSelected: Boolean,
) : EarnFilterNetworkUM() ) : EarnFilterNetworkUM()
data class Network( data class Network(
val id: String,
override val text: TextReference,
val symbol: TextReference,
val iconRes: Int,
override val isSelected: Boolean, override val isSelected: Boolean,
val id: String,
val text: String,
val symbol: String,
val iconRes: Int,
) : EarnFilterNetworkUM() ) : EarnFilterNetworkUM()
} }

View file

@ -1,7 +1,6 @@
package com.tangem.features.feed.ui.earn.state package com.tangem.features.feed.ui.earn.state
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
@Immutable @Immutable
@ -10,16 +9,11 @@ internal data class EarnUM(
val bestOpportunities: EarnBestOpportunitiesUM, val bestOpportunities: EarnBestOpportunitiesUM,
val selectedTypeFilter: EarnFilterTypeUM, val selectedTypeFilter: EarnFilterTypeUM,
val selectedNetworkFilter: EarnFilterNetworkUM, val selectedNetworkFilter: EarnFilterNetworkUM,
val filterByTypeBottomSheet: TangemBottomSheetConfig,
val filterByNetworkBottomSheet: TangemBottomSheetConfig,
val onBackClick: () -> Unit, val onBackClick: () -> Unit,
val onNetworkFilterClick: () -> Unit, val onNetworkFilterClick: () -> Unit,
val onTypeFilterClick: () -> Unit, val onTypeFilterClick: () -> Unit,
) { ) {
val selectedNetworkFilterText: TextReference
get() = selectedNetworkFilter.text
val selectedTypeFilterText: TextReference val selectedTypeFilterText: TextReference
get() = selectedTypeFilter.text get() = selectedTypeFilter.text
} }