Updated on 2026-08-14
This commit is contained in:
parent
27921d8c3a
commit
8ba59403f4
20 changed files with 463 additions and 97 deletions
|
|
@ -207,7 +207,7 @@ interface TangemTechApi {
|
|||
suspend fun transactionEvents(@Body name: TransactionEventBody): ApiResponse<Unit>
|
||||
|
||||
// region Earn
|
||||
@GET("v1/earn/yield/markets")
|
||||
@GET("v1/earn/markets")
|
||||
suspend fun getEarnTokens(
|
||||
@Query("isForEarn") isForEarn: Boolean?,
|
||||
@Query("page") page: String? = null,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ internal class DefaultEarnErrorResolver : EarnErrorResolver {
|
|||
message = throwable.message.orEmpty(),
|
||||
)
|
||||
}
|
||||
else -> EarnError.NotHttpError
|
||||
else -> EarnError.NotHttpError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.earn.converter
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.EarnResponse
|
||||
import com.tangem.domain.models.earn.EarnRewardType
|
||||
import com.tangem.domain.models.earn.EarnToken
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object EarnTokenConverter : Converter<EarnResponse, EarnToken> {
|
||||
|
|
@ -10,12 +12,23 @@ internal object EarnTokenConverter : Converter<EarnResponse, EarnToken> {
|
|||
return EarnToken(
|
||||
apy = value.apy,
|
||||
networkId = value.networkId,
|
||||
rewardType = value.rewardType,
|
||||
type = value.type,
|
||||
rewardType = convertEarnRewardType(value.rewardType),
|
||||
type = convertEarnType(value.type),
|
||||
tokenId = value.token.id,
|
||||
tokenSymbol = value.token.symbol,
|
||||
tokenName = value.token.name,
|
||||
tokenAddress = value.token.address,
|
||||
decimalCount = value.token.decimalCount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertEarnRewardType(type: String): EarnRewardType {
|
||||
return enumValues<EarnRewardType>().find { it.name.equals(type, ignoreCase = true) }
|
||||
?: EarnRewardType.APY
|
||||
}
|
||||
|
||||
private fun convertEarnType(type: String): EarnType {
|
||||
return enumValues<EarnType>().find { it.name.equals(type, ignoreCase = true) }
|
||||
?: EarnType.STAKING
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
package com.tangem.domain.models.earn
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
sealed interface EarnError {
|
||||
|
||||
data class HttpError(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val code: Int,
|
||||
val message: String,
|
||||
) : EarnError
|
||||
|
||||
data object NotHttpError : EarnError
|
||||
data class NotHttpError(
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
) : EarnError
|
||||
}
|
||||
|
|
@ -17,10 +17,19 @@ import kotlinx.serialization.Serializable
|
|||
data class EarnToken(
|
||||
val apy: String,
|
||||
val networkId: String,
|
||||
val rewardType: String,
|
||||
val type: String,
|
||||
val rewardType: EarnRewardType,
|
||||
val type: EarnType,
|
||||
val tokenId: String,
|
||||
val tokenSymbol: String,
|
||||
val tokenName: String,
|
||||
val tokenAddress: String,
|
||||
)
|
||||
val decimalCount: Int?,
|
||||
)
|
||||
|
||||
enum class EarnRewardType {
|
||||
APR, APY
|
||||
}
|
||||
|
||||
enum class EarnType {
|
||||
STAKING, YIELD
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ dependencies {
|
|||
implementation(projects.domain.news)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.earn)
|
||||
|
||||
// FIXME [REDACTED_TASK_KEY]
|
||||
// Remove the "Buy" and "Sell" actions from the redux middleware.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.features.feed.components.earn.DefaultEarnComponent
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
|
||||
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
|
||||
|
|
@ -131,6 +132,14 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
|
|||
override fun onOpenAllNews() {
|
||||
innerRouter.push(FeedEntryChildFactory.Child.NewsList)
|
||||
}
|
||||
|
||||
override fun onOpenEarnPage() {
|
||||
innerRouter.push(
|
||||
FeedEntryChildFactory.Child.Earn(
|
||||
params = DefaultEarnComponent.Params(onBackClick = { onChildBack() }),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val stack: Value<ChildStack<FeedEntryChildFactory.Child, ComposableModularBottomSheetContentComponent>> =
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
|||
import com.tangem.features.feed.components.earn.DefaultEarnComponent
|
||||
import com.tangem.features.feed.components.feed.DefaultFeedComponent
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.list.DefaultMarketsTokenListComponent
|
||||
import com.tangem.features.feed.components.news.details.DefaultNewsDetailsComponent
|
||||
|
|
@ -21,6 +22,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||
private val addToPortfolioPreselectedDataComponent: AddToPortfolioPreselectedDataComponent.Factory,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
|
|
@ -100,6 +102,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
DefaultFeedComponent(
|
||||
appComponentContext = appComponentContext,
|
||||
params = DefaultFeedComponent.FeedParams(feedClickIntents = feedEntryClickIntents),
|
||||
addToPortfolioComponentFactory = addToPortfolioPreselectedDataComponent,
|
||||
)
|
||||
}
|
||||
is Child.Earn -> {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.model.feed.FeedComponentModel
|
||||
import com.tangem.features.feed.model.feed.FeedModelClickIntents
|
||||
import com.tangem.features.feed.ui.feed.FeedList
|
||||
|
|
@ -18,10 +24,18 @@ import com.tangem.features.feed.ui.feed.FeedListHeader
|
|||
internal class DefaultFeedComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: FeedParams,
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioPreselectedDataComponent.Factory,
|
||||
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val feedComponentModel = getOrCreateModel<FeedComponentModel, FeedParams>(params = params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = feedComponentModel.bottomSheetNavigation,
|
||||
serializer = FeedPortfolioRoute.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||
val state by feedComponentModel.state.collectAsStateWithLifecycle()
|
||||
|
|
@ -40,11 +54,28 @@ internal class DefaultFeedComponent(
|
|||
}
|
||||
}
|
||||
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
val state by feedComponentModel.state.collectAsStateWithLifecycle()
|
||||
FeedList(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
config: FeedPortfolioRoute,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
is FeedPortfolioRoute.AddToPortfolio -> {
|
||||
addToPortfolioComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AddToPortfolioPreselectedDataComponent.Params(
|
||||
tokenToAdd = config.tokenToAdd,
|
||||
callback = feedComponentModel.addToPortfolioCallback,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FeedParams(val feedClickIntents: FeedModelClickIntents)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.features.feed.components.market.details.portfolio.add.impl.di
|
|||
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.impl.DefaultAddToPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.impl.DefaultAddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.impl.ui.DefaultAddToPortfolioManager
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -18,4 +20,9 @@ internal interface AddToPortfolioComponentModule {
|
|||
|
||||
@Binds
|
||||
fun bindAddToPortfolioManagerFactory(factory: DefaultAddToPortfolioManager.Factory): AddToPortfolioManager.Factory
|
||||
|
||||
@Binds
|
||||
fun bindAddToPortfolioPreselectedDataComponent(
|
||||
factory: DefaultAddToPortfolioPreselectedDataComponent.Factory,
|
||||
): AddToPortfolioPreselectedDataComponent.Factory
|
||||
}
|
||||
|
|
@ -2,6 +2,11 @@ package com.tangem.features.feed.model.feed
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
|
|
@ -12,20 +17,28 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.earn.usecase.FetchTopEarnTokensUseCase
|
||||
import com.tangem.domain.earn.usecase.GetTopEarnTokensUseCase
|
||||
import com.tangem.domain.markets.GetTopFiveMarketTokenUseCase
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.domain.markets.TokenMarketListConfig
|
||||
import com.tangem.domain.markets.toSerializableParam
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.earn.EarnTokenWithCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.news.usecase.FetchTrendingNewsUseCase
|
||||
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
|
||||
import com.tangem.features.feed.components.feed.DefaultFeedComponent
|
||||
import com.tangem.features.feed.components.feed.FeedPortfolioRoute
|
||||
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.model.feed.analytics.FeedAnalyticsEvent
|
||||
import com.tangem.features.feed.model.feed.state.FeedMarketsBatchFlowManager
|
||||
import com.tangem.features.feed.model.feed.state.FeedStateController
|
||||
import com.tangem.features.feed.model.feed.state.transformers.UpdateGlobalFeedStateTransformer
|
||||
import com.tangem.features.feed.model.feed.state.transformers.UpdateMarketChartsTransformer
|
||||
import com.tangem.features.feed.model.feed.state.transformers.UpdateTrendingNewsStateTransformer
|
||||
import com.tangem.features.feed.model.feed.state.transformers.*
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -41,22 +54,26 @@ import javax.inject.Inject
|
|||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class FeedComponentModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val fetchTrendingNewsUseCase: FetchTrendingNewsUseCase,
|
||||
private val manageTrendingNewsUseCase: ManageTrendingNewsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val stateController: FeedStateController,
|
||||
private val feedFeatureToggle: FeedFeatureToggle,
|
||||
private val fetchTopEarnTokensUseCase: FetchTopEarnTokensUseCase,
|
||||
private val getTopEarnTokensUseCase: GetTopEarnTokensUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<DefaultFeedComponent.FeedParams>()
|
||||
|
||||
private var quotesUpdateJob: Job? = null
|
||||
|
||||
private val params = paramsContainer.require<DefaultFeedComponent.FeedParams>()
|
||||
|
||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}.stateIn(
|
||||
|
|
@ -72,7 +89,22 @@ internal class FeedComponentModel @Inject constructor(
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
internal val state: StateFlow<FeedListUM>
|
||||
val bottomSheetNavigation: SlotNavigation<FeedPortfolioRoute> = SlotNavigation()
|
||||
|
||||
val addToPortfolioCallback = object : AddToPortfolioPreselectedDataComponent.Callback {
|
||||
override fun onDismiss() = bottomSheetNavigation.dismiss()
|
||||
override fun onSuccess(addedToken: CryptoCurrency, walletId: UserWalletId) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
appRouter.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
userWalletId = walletId,
|
||||
currency = addedToken,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val state: StateFlow<FeedListUM>
|
||||
get() = stateController.uiState
|
||||
|
||||
val isVisibleOnScreen = MutableStateFlow(false)
|
||||
|
|
@ -81,29 +113,38 @@ internal class FeedComponentModel @Inject constructor(
|
|||
initializeState()
|
||||
updateCallbacks()
|
||||
fetchTrendingNews()
|
||||
fetchEarnData()
|
||||
fetchCharts()
|
||||
subscribeOnCurrencyUpdate()
|
||||
loadCharts()
|
||||
subscribeOnDataState()
|
||||
}
|
||||
|
||||
private fun subscribeOnDataState() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
combine(
|
||||
flow = marketsBatchFlowManager.itemsByOrder,
|
||||
flow2 = marketsBatchFlowManager.loadingStatesByOrder,
|
||||
flow3 = marketsBatchFlowManager.errorStatesByOrder,
|
||||
flow4 = manageTrendingNewsUseCase.observeTrendingNews(),
|
||||
) { itemsByOrder, loadingStatesByOrder, errorStatesByOrder, trendingNewsResult ->
|
||||
flow5 = getTopEarnTokensUseCase(),
|
||||
) { itemsByOrder, loadingStatesByOrder, errorStatesByOrder, trendingNewsResult, earnResult ->
|
||||
val globalStateTransformer = UpdateGlobalFeedStateTransformer(
|
||||
loadingStatesByOrder = loadingStatesByOrder,
|
||||
errorStatesByOrder = errorStatesByOrder,
|
||||
trendingNewsResult = trendingNewsResult,
|
||||
earnResult = earnResult,
|
||||
onRetryClicked = {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
stateController.update { currentState ->
|
||||
currentState.copy(globalState = GlobalFeedState.Loading)
|
||||
}
|
||||
fetchTrendingNewsUseCase.invoke()
|
||||
fetchTrendingNews()
|
||||
fetchEarnData()
|
||||
marketsBatchFlowManager.reloadAll()
|
||||
}
|
||||
},
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
feedFeatureToggle = feedFeatureToggle,
|
||||
)
|
||||
|
||||
val currentState = stateController.value
|
||||
|
|
@ -123,13 +164,15 @@ internal class FeedComponentModel @Inject constructor(
|
|||
),
|
||||
UpdateTrendingNewsStateTransformer(
|
||||
result = trendingNewsResult,
|
||||
onRetryClicked = {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
fetchTrendingNewsUseCase.invoke()
|
||||
}
|
||||
},
|
||||
onRetryClicked = ::fetchTrendingNews,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
),
|
||||
UpdateEarnStateTransformer(
|
||||
isEarnEnabled = feedFeatureToggle.isEarnBlockEnabled,
|
||||
onItemClick = ::handleEarnTokenClick,
|
||||
onRetryClick = ::fetchEarnData,
|
||||
earnResult = earnResult,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -144,12 +187,6 @@ internal class FeedComponentModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchTrendingNews() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
fetchTrendingNewsUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrencyUpdate() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
currentAppCurrency.drop(1).collect {
|
||||
|
|
@ -158,7 +195,21 @@ internal class FeedComponentModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadCharts() {
|
||||
private fun fetchTrendingNews() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
fetchTrendingNewsUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchEarnData() {
|
||||
if (!feedFeatureToggle.isEarnBlockEnabled) return
|
||||
modelScope.launch(dispatchers.default) {
|
||||
stateController.update(UpdateEarnLoadingStateTransformer())
|
||||
fetchTopEarnTokensUseCase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchCharts() {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
TokenMarketListConfig.Order.entries.forEach { order ->
|
||||
marketsBatchFlowManager.getOnLastBatchLoadedSuccessFlow(order)?.collect { batchKey ->
|
||||
|
|
@ -198,6 +249,7 @@ internal class FeedComponentModel @Inject constructor(
|
|||
onSliderEndReached = {
|
||||
analyticsEventHandler.send(FeedAnalyticsEvent.NewsCarouselEndReached())
|
||||
},
|
||||
onOpenEarnPageClick = params.feedClickIntents::onOpenEarnPage,
|
||||
),
|
||||
news = NewsUM(
|
||||
content = persistentListOf(),
|
||||
|
|
@ -214,14 +266,41 @@ internal class FeedComponentModel @Inject constructor(
|
|||
currentSortByType = SortByTypeUM.TopGainers,
|
||||
),
|
||||
globalState = GlobalFeedState.Loading,
|
||||
earnListUM = if (feedFeatureToggle.isEarnBlockEnabled) {
|
||||
EarnListUM.Loading
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCurrentDate(): String {
|
||||
val localDate = DateTime(DateTime.now(), DateTimeZone.getDefault())
|
||||
return DateTimeFormatters.formatDate(formatter = DateTimeFormatters.dateDMMM, date = localDate)
|
||||
private fun startQuotesUpdateTimer() {
|
||||
quotesUpdateJob?.cancel()
|
||||
quotesUpdateJob = modelScope.launch {
|
||||
while (true) {
|
||||
delay(DELAY_TO_FETCH_QUOTES)
|
||||
isVisibleOnScreen.first { it }
|
||||
marketsBatchFlowManager.updateQuotes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCallbacks() {
|
||||
stateController.update { feedListUM ->
|
||||
feedListUM.copy(
|
||||
feedListCallbacks = feedListUM.feedListCallbacks.copy(
|
||||
onSortTypeClick = ::handleSortTypeClicked,
|
||||
onMarketItemClick = ::handleMarketItemClicked,
|
||||
onMarketOpenClick = ::handleMarketOpenClicked,
|
||||
onArticleClick = ::handleArticleClicked,
|
||||
onOpenAllNews = ::handleOpenAllNews,
|
||||
onOpenEarnPageClick = params.feedClickIntents::onOpenEarnPage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* start of clicks area */
|
||||
private fun handleSortTypeClicked(sortByType: SortByTypeUM) {
|
||||
stateController.update { currentState ->
|
||||
val updatedCharts = currentState.marketChartConfig.marketCharts.mapValues { (chartSortType, chart) ->
|
||||
|
|
@ -249,31 +328,6 @@ internal class FeedComponentModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun startQuotesUpdateTimer() {
|
||||
quotesUpdateJob?.cancel()
|
||||
quotesUpdateJob = modelScope.launch {
|
||||
while (true) {
|
||||
delay(DELAY_TO_FETCH_QUOTES)
|
||||
isVisibleOnScreen.first { it }
|
||||
marketsBatchFlowManager.updateQuotes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCallbacks() {
|
||||
stateController.update { feedListUM ->
|
||||
feedListUM.copy(
|
||||
feedListCallbacks = feedListUM.feedListCallbacks.copy(
|
||||
onSortTypeClick = ::handleSortTypeClicked,
|
||||
onMarketItemClick = ::handleMarketItemClicked,
|
||||
onMarketOpenClick = ::handleMarketOpenClicked,
|
||||
onArticleClick = ::handleArticleClicked,
|
||||
onOpenAllNews = ::handleOpenAllNews,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMarketItemClicked(item: MarketsListItemUM) {
|
||||
val tokenMarket = marketsBatchFlowManager.getTokenMarketById(item.id)
|
||||
if (tokenMarket != null) {
|
||||
|
|
@ -342,6 +396,26 @@ internal class FeedComponentModel @Inject constructor(
|
|||
params.feedClickIntents.onOpenAllNews()
|
||||
}
|
||||
|
||||
private fun handleEarnTokenClick(earnTokenWithCurrency: EarnTokenWithCurrency) {
|
||||
bottomSheetNavigation.activate(
|
||||
FeedPortfolioRoute.AddToPortfolio(
|
||||
tokenToAdd = AddToPortfolioPreselectedDataComponent.TokenToAdd(
|
||||
network = TokenMarketInfo.Network(
|
||||
networkId = earnTokenWithCurrency.earnToken.networkId,
|
||||
isExchangeable = false,
|
||||
contractAddress = earnTokenWithCurrency.earnToken.tokenAddress,
|
||||
decimalCount = earnTokenWithCurrency.earnToken.decimalCount,
|
||||
),
|
||||
id = CryptoCurrency.RawID(earnTokenWithCurrency.earnToken.tokenId),
|
||||
name = earnTokenWithCurrency.earnToken.tokenName,
|
||||
symbol = earnTokenWithCurrency.earnToken.tokenSymbol,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
/* end of clicks area */
|
||||
|
||||
/* start of utils area */
|
||||
private fun SortByTypeUM.toOrder(): TokenMarketListConfig.Order {
|
||||
return when (this) {
|
||||
SortByTypeUM.Rating -> TokenMarketListConfig.Order.ByRating
|
||||
|
|
@ -354,6 +428,12 @@ internal class FeedComponentModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCurrentDate(): String {
|
||||
val localDate = DateTime(DateTime.now(), DateTimeZone.getDefault())
|
||||
return DateTimeFormatters.formatDate(formatter = DateTimeFormatters.dateDMMM, date = localDate)
|
||||
}
|
||||
/* end of utils area */
|
||||
|
||||
companion object {
|
||||
private const val DELAY_TO_FETCH_QUOTES = 60_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,6 @@ internal interface FeedModelClickIntents {
|
|||
paginationConfig: NewsListConfig? = null,
|
||||
)
|
||||
fun onOpenAllNews()
|
||||
|
||||
fun onOpenEarnPage()
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.features.feed.model.feed.state
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
|
||||
import com.tangem.features.feed.model.feed.state.transformers.FeedListUMTransformer
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import kotlinx.collections.immutable.persistentHashMapOf
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -13,7 +16,9 @@ import kotlinx.coroutines.flow.update
|
|||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class FeedStateController @Inject constructor() {
|
||||
internal class FeedStateController @Inject constructor(
|
||||
private val feedFeatureToggle: FeedFeatureToggle,
|
||||
) {
|
||||
|
||||
private val mutableUiState: MutableStateFlow<FeedListUM> = MutableStateFlow(value = getInitialState())
|
||||
|
||||
|
|
@ -33,15 +38,11 @@ internal class FeedStateController @Inject constructor() {
|
|||
transformers.forEach { mutableUiState.update(function = it::transform) }
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
mutableUiState.update { getInitialState() }
|
||||
}
|
||||
|
||||
private fun getInitialState(): FeedListUM {
|
||||
return FeedListUM(
|
||||
currentDate = "",
|
||||
feedListSearchBar = FeedListSearchBar(
|
||||
placeholderText = com.tangem.core.ui.extensions.TextReference.EMPTY,
|
||||
placeholderText = TextReference.EMPTY,
|
||||
onBarClick = {},
|
||||
),
|
||||
feedListCallbacks = FeedListCallbacks(
|
||||
|
|
@ -53,6 +54,7 @@ internal class FeedStateController @Inject constructor() {
|
|||
onSortTypeClick = {},
|
||||
onSliderScroll = {},
|
||||
onSliderEndReached = {},
|
||||
onOpenEarnPageClick = {},
|
||||
),
|
||||
news = NewsUM(
|
||||
content = persistentListOf(),
|
||||
|
|
@ -65,6 +67,11 @@ internal class FeedStateController @Inject constructor() {
|
|||
currentSortByType = SortByTypeUM.Trending,
|
||||
),
|
||||
globalState = GlobalFeedState.Loading,
|
||||
earnListUM = if (feedFeatureToggle.isEarnBlockEnabled) {
|
||||
EarnListUM.Loading
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.feed.model.feed.state.transformers
|
||||
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListUM
|
||||
|
||||
internal class UpdateEarnLoadingStateTransformer : FeedListUMTransformer {
|
||||
|
||||
override fun transform(prevState: FeedListUM): FeedListUM {
|
||||
return prevState.copy(earnListUM = EarnListUM.Loading)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.tangem.features.feed.model.feed.state.transformers
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.models.earn.EarnRewardType
|
||||
import com.tangem.domain.models.earn.EarnTokenWithCurrency
|
||||
import com.tangem.domain.models.earn.EarnTopToken
|
||||
import com.tangem.domain.models.earn.EarnType
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class UpdateEarnStateTransformer(
|
||||
private val isEarnEnabled: Boolean,
|
||||
private val onItemClick: (EarnTokenWithCurrency) -> Unit,
|
||||
private val onRetryClick: () -> Unit,
|
||||
private val earnResult: EarnTopToken?,
|
||||
) : FeedListUMTransformer {
|
||||
|
||||
private val earnTokenWithCurrencyConverter = EarnTokenWithCurrencyConverter(onItemClick = onItemClick)
|
||||
|
||||
override fun transform(prevState: FeedListUM): FeedListUM {
|
||||
if (!isEarnEnabled) return handleEmptyState(prevState)
|
||||
return when (earnResult) {
|
||||
null -> {
|
||||
handleEmptyState(prevState)
|
||||
}
|
||||
else -> {
|
||||
earnResult.fold(
|
||||
ifLeft = {
|
||||
handleErrorState(prevState)
|
||||
},
|
||||
ifRight = { earnTokensWithCurrency ->
|
||||
handleDataState(
|
||||
currentState = prevState,
|
||||
earnTokensWithCurrency = earnTokensWithCurrency,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleEmptyState(currentState: FeedListUM): FeedListUM {
|
||||
return currentState.copy(earnListUM = null)
|
||||
}
|
||||
|
||||
private fun handleErrorState(currentState: FeedListUM): FeedListUM {
|
||||
return currentState.copy(earnListUM = EarnListUM.Error(onRetryClick))
|
||||
}
|
||||
|
||||
private fun handleDataState(
|
||||
currentState: FeedListUM,
|
||||
earnTokensWithCurrency: List<EarnTokenWithCurrency>,
|
||||
): FeedListUM {
|
||||
return currentState.copy(
|
||||
earnListUM = EarnListUM.Content(
|
||||
items = earnTokensWithCurrency
|
||||
.sortedWith(
|
||||
compareByDescending<EarnTokenWithCurrency> {
|
||||
it.earnToken.apy.toBigDecimalOrNull() ?: BigDecimal.ZERO
|
||||
}.thenBy { it.earnToken.tokenName },
|
||||
)
|
||||
.map { earnToken ->
|
||||
earnTokenWithCurrencyConverter.convert(earnToken)
|
||||
}
|
||||
.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private class EarnTokenWithCurrencyConverter(
|
||||
private val onItemClick: (EarnTokenWithCurrency) -> Unit,
|
||||
) : Converter<EarnTokenWithCurrency, EarnListItemUM> {
|
||||
override fun convert(value: EarnTokenWithCurrency): EarnListItemUM {
|
||||
return EarnListItemUM(
|
||||
network = TextReference.Str(value.networkName),
|
||||
symbol = TextReference.Str(value.earnToken.tokenSymbol),
|
||||
tokenName = TextReference.Str(value.earnToken.tokenName),
|
||||
currencyIconState = CryptoCurrencyToIconStateConverter().convert(value.cryptoCurrency),
|
||||
earnValue = when (value.earnToken.rewardType) {
|
||||
EarnRewardType.APR -> TextReference.Res(
|
||||
id = R.string.staking_apr_earn_badge,
|
||||
formatArgs = wrappedList(convertPercent(value.earnToken.apy)),
|
||||
)
|
||||
EarnRewardType.APY -> TextReference.Res(
|
||||
id = R.string.yield_module_earn_badge,
|
||||
formatArgs = wrappedList(convertPercent(value.earnToken.apy)),
|
||||
)
|
||||
},
|
||||
earnType = when (value.earnToken.type) {
|
||||
EarnType.STAKING -> TextReference.Res(R.string.common_staking)
|
||||
EarnType.YIELD -> TextReference.Res(R.string.common_yield_mode)
|
||||
},
|
||||
onItemClick = { onItemClick(value) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPercent(value: String): TextReference {
|
||||
val percent = BigDecimal(value).format { percent(withPercentSign = false) }
|
||||
return TextReference.Str(percent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +1,58 @@
|
|||
package com.tangem.features.feed.model.feed.state.transformers
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.models.earn.EarnTopToken
|
||||
import com.tangem.domain.models.news.TrendingNews
|
||||
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
|
||||
import com.tangem.features.feed.model.feed.analytics.FeedAnalyticsEvent
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.FeedListUM
|
||||
import com.tangem.features.feed.ui.feed.state.GlobalFeedState
|
||||
import com.tangem.features.feed.ui.feed.state.NewsUMState
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class UpdateGlobalFeedStateTransformer(
|
||||
private val loadingStatesByOrder: Map<SortByTypeUM, Boolean>,
|
||||
private val errorStatesByOrder: Map<SortByTypeUM, Throwable?>,
|
||||
private val trendingNewsResult: TrendingNews,
|
||||
private val earnResult: EarnTopToken?,
|
||||
private val onRetryClicked: () -> Unit,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val feedFeatureToggle: FeedFeatureToggle,
|
||||
) : FeedListUMTransformer {
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override fun transform(prevState: FeedListUM): FeedListUM {
|
||||
val previousGlobalState = prevState.globalState
|
||||
val blockStates = buildList {
|
||||
add(getNewsState(prevState))
|
||||
add(getChartsState())
|
||||
if (feedFeatureToggle.isEarnBlockEnabled) {
|
||||
add(getEarnState(prevState))
|
||||
}
|
||||
}
|
||||
|
||||
val isNewsLoading = prevState.news.newsUMState == NewsUMState.LOADING
|
||||
val isNewsError = trendingNewsResult is TrendingNews.Error ||
|
||||
prevState.news.newsUMState == NewsUMState.ERROR
|
||||
val newGlobalState = when {
|
||||
blockStates.all { it == BlockState.ERROR } -> {
|
||||
if (prevState.globalState !is GlobalFeedState.Error) {
|
||||
sendErrorAnalytics()
|
||||
}
|
||||
GlobalFeedState.Error(onRetryClicked = onRetryClicked)
|
||||
}
|
||||
blockStates.any { it == BlockState.LOADING } -> GlobalFeedState.Loading
|
||||
else -> GlobalFeedState.Content
|
||||
}
|
||||
|
||||
return prevState.copy(globalState = newGlobalState)
|
||||
}
|
||||
|
||||
private fun areAllChartsError(): Boolean {
|
||||
val hasLoadingInCharts = loadingStatesByOrder.values.any { it }
|
||||
val hasErrorInMarketChart = errorStatesByOrder[SortByTypeUM.Rating] != null
|
||||
val hasErrorInMarketPulseChart = errorStatesByOrder
|
||||
.any { it.key != SortByTypeUM.Rating && it.value != null }
|
||||
|
||||
val areAllChartsLoading = loadingStatesByOrder.values.isNotEmpty() && loadingStatesByOrder.values.all { it }
|
||||
val areAllChartsError = when {
|
||||
hasErrorInMarketChart && hasErrorInMarketPulseChart -> true
|
||||
hasErrorInMarketPulseChart && hasLoadingInCharts -> true
|
||||
hasErrorInMarketChart && hasLoadingInCharts -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val newGlobalState = when {
|
||||
isNewsError && areAllChartsLoading -> GlobalFeedState.Loading
|
||||
isNewsError && areAllChartsError -> {
|
||||
if (previousGlobalState !is GlobalFeedState.Error) {
|
||||
sendErrorAnalytics()
|
||||
}
|
||||
GlobalFeedState.Error(
|
||||
onRetryClicked = onRetryClicked,
|
||||
)
|
||||
}
|
||||
isNewsLoading && areAllChartsError -> GlobalFeedState.Loading
|
||||
isNewsLoading && areAllChartsLoading -> GlobalFeedState.Loading
|
||||
else -> GlobalFeedState.Content
|
||||
}
|
||||
return prevState.copy(globalState = newGlobalState)
|
||||
return hasErrorInMarketChart && hasErrorInMarketPulseChart ||
|
||||
hasErrorInMarketPulseChart && hasLoadingInCharts ||
|
||||
hasErrorInMarketChart && hasLoadingInCharts
|
||||
}
|
||||
|
||||
private fun sendErrorAnalytics() {
|
||||
|
|
@ -59,4 +60,38 @@ internal class UpdateGlobalFeedStateTransformer(
|
|||
FeedAnalyticsEvent.AllWidgetsLoadError(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNewsState(prevState: FeedListUM): BlockState {
|
||||
val isError = trendingNewsResult is TrendingNews.Error || prevState.news.newsUMState == NewsUMState.ERROR
|
||||
if (isError) return BlockState.ERROR
|
||||
|
||||
val isLoading = prevState.news.newsUMState == NewsUMState.LOADING && trendingNewsResult !is TrendingNews.Data
|
||||
if (isLoading) return BlockState.LOADING
|
||||
|
||||
return BlockState.CONTENT
|
||||
}
|
||||
|
||||
private fun getChartsState(): BlockState {
|
||||
if (areAllChartsError()) return BlockState.ERROR
|
||||
|
||||
val isLoading = loadingStatesByOrder.values.isNotEmpty() && loadingStatesByOrder.values.all { it }
|
||||
if (isLoading) return BlockState.LOADING
|
||||
|
||||
return BlockState.CONTENT
|
||||
}
|
||||
|
||||
private fun getEarnState(prevState: FeedListUM): BlockState {
|
||||
val isError = when (earnResult) {
|
||||
null -> false
|
||||
else -> earnResult.isLeft()
|
||||
}
|
||||
if (isError) return BlockState.ERROR
|
||||
|
||||
val isLoading = prevState.earnListUM is EarnListUM.Loading && earnResult == null
|
||||
if (isLoading) return BlockState.LOADING
|
||||
|
||||
return BlockState.CONTENT
|
||||
}
|
||||
|
||||
private enum class BlockState { LOADING, ERROR, CONTENT }
|
||||
}
|
||||
|
|
@ -147,10 +147,8 @@ private fun FeedListContent(state: FeedListUM, modifier: Modifier = Modifier) {
|
|||
)
|
||||
|
||||
EarnBlock(
|
||||
onSeeAllClick = {
|
||||
// TODO handle in [REDACTED_TASK_KEY]
|
||||
},
|
||||
earnListUM = null, // TODO in [REDACTED_TASK_KEY]
|
||||
onSeeAllClick = state.feedListCallbacks.onOpenEarnPageClick,
|
||||
earnListUM = state.earnListUM,
|
||||
)
|
||||
|
||||
MarketPulseBlock(
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@ package com.tangem.features.feed.ui.feed.preview
|
|||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.common.ui.news.ArticleConfigUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListItemUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import com.tangem.features.feed.ui.feed.state.*
|
||||
import kotlinx.collections.immutable.*
|
||||
|
||||
|
|
@ -34,6 +39,7 @@ internal object FeedListPreviewDataProvider {
|
|||
onSortTypeClick = {},
|
||||
onSliderScroll = {},
|
||||
onSliderEndReached = {},
|
||||
onOpenEarnPageClick = {},
|
||||
),
|
||||
news = NewsUM(
|
||||
content = articles.filter { it.isTrending.not() }.toImmutableList(),
|
||||
|
|
@ -45,6 +51,9 @@ internal object FeedListPreviewDataProvider {
|
|||
marketCharts = createMarketCharts(marketItems, includeErrorState = false),
|
||||
currentSortByType = SortByTypeUM.TopGainers,
|
||||
),
|
||||
earnListUM = EarnListUM.Content(
|
||||
items = createEarnListItemsUM(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -250,4 +259,25 @@ internal object FeedListPreviewDataProvider {
|
|||
updateTimestamp = 0,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createEarnListItemsUM(): ImmutableList<EarnListItemUM> {
|
||||
return List(5) {
|
||||
EarnListItemUM(
|
||||
network = stringReference("Ethereum"),
|
||||
symbol = stringReference("USDT"),
|
||||
tokenName = stringReference("TETHER"),
|
||||
currencyIconState = CurrencyIconState.TokenIcon(
|
||||
url = null,
|
||||
topBadgeIconResId = R.drawable.img_eth_22,
|
||||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
earnValue = stringReference("APY 6.54%"),
|
||||
earnType = stringReference("Yield"),
|
||||
onItemClick = {},
|
||||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.ui.markets.models.MarketsListItemUM
|
|||
import com.tangem.common.ui.news.ArticleConfigUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.feed.model.market.list.state.SortByTypeUM
|
||||
import com.tangem.features.feed.ui.earn.state.EarnListUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -17,6 +18,7 @@ internal data class FeedListUM(
|
|||
val trendingArticle: ArticleConfigUM?,
|
||||
val marketChartConfig: MarketChartConfig,
|
||||
val globalState: GlobalFeedState = GlobalFeedState.Content,
|
||||
val earnListUM: EarnListUM?,
|
||||
)
|
||||
|
||||
internal data class FeedListCallbacks(
|
||||
|
|
@ -28,6 +30,7 @@ internal data class FeedListCallbacks(
|
|||
val onSortTypeClick: (sortBy: SortByTypeUM) -> Unit,
|
||||
val onSliderScroll: () -> Unit,
|
||||
val onSliderEndReached: () -> Unit,
|
||||
val onOpenEarnPageClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal data class FeedListSearchBar(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue