Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-16 11:55:21 +03:00
commit 246ab4b47e
25 changed files with 362 additions and 3 deletions

View file

@ -342,6 +342,8 @@ dependencies {
implementation(projects.features.tokenRecieve.impl) implementation(projects.features.tokenRecieve.impl)
implementation(projects.features.yieldSupply.api) implementation(projects.features.yieldSupply.api)
implementation(projects.features.yieldSupply.impl) implementation(projects.features.yieldSupply.impl)
implementation(projects.features.polymarket.api)
implementation(projects.features.polymarket.impl)
implementation(projects.features.approval.api) implementation(projects.features.approval.api)
implementation(projects.features.approval.impl) implementation(projects.features.approval.impl)
implementation(projects.features.forYou.api) implementation(projects.features.forYou.api)

View file

@ -46,6 +46,7 @@ import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent
import com.tangem.features.wallet.WalletEntryComponent import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.features.walletconnect.components.WalletConnectEntryComponent import com.tangem.features.walletconnect.components.WalletConnectEntryComponent
import com.tangem.features.polymarket.api.PolymarketComponent
import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent import com.tangem.features.yield.supply.api.YieldSupplyEntryComponent
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
@ -118,6 +119,7 @@ internal class ChildFactory @Inject constructor(
private val kycComponentFactory: KycComponent.Factory, private val kycComponentFactory: KycComponent.Factory,
private val surveyComponentFactory: SurveyComponent.Factory, private val surveyComponentFactory: SurveyComponent.Factory,
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory, private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
private val polymarketComponentFactory: PolymarketComponent.Factory,
private val feedEntryComponentFactory: FeedEntryComponent.Factory, private val feedEntryComponentFactory: FeedEntryComponent.Factory,
private val addressBookComponentFactory: AddressBookComponent.Factory, private val addressBookComponentFactory: AddressBookComponent.Factory,
) { ) {
@ -748,6 +750,13 @@ internal class ChildFactory @Inject constructor(
componentFactory = yieldSupplyEntryComponentFactory, componentFactory = yieldSupplyEntryComponentFactory,
) )
} }
is AppRoute.Polymarket -> {
createComponentChild(
context = context,
params = PolymarketComponent.Params(userWalletId = route.userWalletId),
componentFactory = polymarketComponentFactory,
)
}
is AppRoute.NewsDetails -> { is AppRoute.NewsDetails -> {
createComponentChild( createComponentChild(
context = context, context = context,

View file

@ -558,6 +558,11 @@ sealed class AppRoute(val path: String) : Route {
val apy: String, val apy: String,
) : AppRoute(path = "/yield_supply_entry/${userWalletId.stringValue}/${cryptoCurrency.symbol}") ) : AppRoute(path = "/yield_supply_entry/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
@Serializable
data class Polymarket(
val userWalletId: UserWalletId,
) : AppRoute(path = "/polymarket/${userWalletId.stringValue}")
@Serializable @Serializable
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId") data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")

View file

@ -182,5 +182,9 @@
{ {
"name": "TWI_1192_TANGEM_PAY_CASHBACK_ENABLED", "name": "TWI_1192_TANGEM_PAY_CASHBACK_ENABLED",
"version": "undefined" "version": "undefined"
},
{
"name": "AND_16204_POLYMARKET_ENABLED",
"version": "undefined"
} }
] ]

View file

@ -874,6 +874,8 @@
<string name="main_add_funds_promo_description">Buy or receive crypto to start using your wallet.</string> <string name="main_add_funds_promo_description">Buy or receive crypto to start using your wallet.</string>
<string name="main_add_funds_promo_title">Get your first crypto</string> <string name="main_add_funds_promo_title">Get your first crypto</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string> <string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string>
<string name="prediction_account_subtitle">Add funds &amp; start predict</string>
<string name="prediction_account_title">Prediction account</string>
<string name="main_manage_tokens">Manage tokens</string> <string name="main_manage_tokens">Manage tokens</string>
<string name="main_qr_scan_hint">Scan QR code to send funds or connect to an app</string> <string name="main_qr_scan_hint">Scan QR code to send funds or connect to an app</string>
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string> <string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>

View file

@ -0,0 +1,21 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.features.polymarket.api"
}
dependencies {
/** Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
/** Domain */
implementation(projects.domain.models)
/** Compose */
implementation(deps.compose.runtime)
}

View file

@ -0,0 +1,14 @@
package com.tangem.features.polymarket.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.wallet.UserWalletId
interface PolymarketComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
)
interface Factory : ComponentFactory<Params, PolymarketComponent>
}

View file

@ -0,0 +1,5 @@
package com.tangem.features.polymarket.api
interface PolymarketFeatureToggles {
val isPolymarketEnabled: Boolean
}

View file

@ -0,0 +1,32 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
alias(deps.plugins.hilt.android)
id("configuration")
}
android {
namespace = "com.tangem.features.polymarket.impl"
}
dependencies {
/** Feature */
implementation(projects.features.polymarket.api)
/** Core */
implementation(projects.core.configToggles)
implementation(projects.core.decompose)
implementation(projects.core.ui)
/** Compose */
implementation(deps.compose.foundation)
implementation(deps.compose.runtime)
implementation(deps.compose.material3)
implementation(deps.compose.ui)
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}

View file

@ -0,0 +1,35 @@
package com.tangem.features.polymarket.impl
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.features.polymarket.api.PolymarketComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultPolymarketComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: PolymarketComponent.Params,
) : PolymarketComponent, AppComponentContext by appComponentContext {
@Composable
override fun Content(modifier: Modifier) {
// TODO([REDACTED_TASK_KEY]): real Polymarket prediction account UI
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = params.userWalletId.stringValue)
}
}
@AssistedFactory
interface Factory : PolymarketComponent.Factory {
override fun create(
context: AppComponentContext,
params: PolymarketComponent.Params,
): DefaultPolymarketComponent
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.features.polymarket.impl.di
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.polymarket.api.PolymarketComponent
import com.tangem.features.polymarket.api.PolymarketFeatureToggles
import com.tangem.features.polymarket.impl.DefaultPolymarketComponent
import com.tangem.features.polymarket.impl.featuretoggles.DefaultPolymarketFeatureToggles
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface PolymarketBindsModule {
@Binds
@Singleton
fun providePolymarketComponentFactory(impl: DefaultPolymarketComponent.Factory): PolymarketComponent.Factory
}
@Module
@InstallIn(SingletonComponent::class)
internal object PolymarketFeatureTogglesModule {
@Provides
@Singleton
fun providePolymarketFeatureToggles(featureTogglesManager: FeatureTogglesManager): PolymarketFeatureToggles {
return DefaultPolymarketFeatureToggles(featureTogglesManager = featureTogglesManager)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.features.polymarket.impl.featuretoggles
import com.tangem.core.configtoggle.FeatureToggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.polymarket.api.PolymarketFeatureToggles
import javax.inject.Inject
internal class DefaultPolymarketFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) : PolymarketFeatureToggles {
override val isPolymarketEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16204_POLYMARKET_ENABLED)
}

View file

@ -135,6 +135,7 @@ dependencies {
api(projects.features.feed.api) api(projects.features.feed.api)
api(projects.features.hotWallet.api) api(projects.features.hotWallet.api)
api(projects.features.promoBanners.api) api(projects.features.promoBanners.api)
api(projects.features.polymarket.api)
api(projects.features.pushNotifications.api) api(projects.features.pushNotifications.api)
api(projects.features.pushNotificationSettings.api) api(projects.features.pushNotificationSettings.api)
api(projects.features.send.api) api(projects.features.send.api)

View file

@ -134,6 +134,11 @@ internal class WalletClickIntents @Inject constructor(
router.openAddFunds(userWalletId) router.openAddFunds(userWalletId)
} }
fun onPredictionAccountClick(userWalletId: UserWalletId) {
// TODO([REDACTED_TASK_KEY]): add analytics for prediction account entry point
router.openPolymarket(userWalletId)
}
private fun refreshMultiCurrencyContent(showRefreshState: Boolean) { private fun refreshMultiCurrencyContent(showRefreshState: Boolean) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return

View file

@ -132,6 +132,10 @@ internal class DefaultWalletRouter @Inject constructor(
) )
} }
override fun openPolymarket(userWalletId: UserWalletId) {
router.push(AppRoute.Polymarket(userWalletId = userWalletId))
}
override fun isWalletLastScreen(): Boolean { override fun isWalletLastScreen(): Boolean {
return router.stack.lastOrNull() is AppRoute.Wallet return router.stack.lastOrNull() is AppRoute.Wallet
} }

View file

@ -125,4 +125,7 @@ internal interface InnerWalletRouter {
/** Open Transfer screen */ /** Open Transfer screen */
fun openTransfer(userWalletId: UserWalletId) fun openTransfer(userWalletId: UserWalletId)
/** Open Polymarket (prediction account) entry screen */
fun openPolymarket(userWalletId: UserWalletId)
} }

View file

@ -95,6 +95,15 @@ internal sealed interface TokensListItemUM2 {
override val tokenRowUM: TangemTokenRowUM, override val tokenRowUM: TangemTokenRowUM,
) : TokensListItemUM2 ) : TokensListItemUM2
/**
* Synthetic, feature-flag-gated entry-point row for the Polymarket prediction account.
* Rendered as a standalone rounded card above the real account rows. Not backed by a domain
* account yet will become an [AccountStatus]-backed row once the external contract lands.
*/
data class Prediction(
override val tokenRowUM: TangemTokenRowUM,
) : TokensListItemUM2
data class Portfolio( data class Portfolio(
override val tokenRowUM: TangemTokenRowUM, override val tokenRowUM: TangemTokenRowUM,
val onEmptyClick: () -> Unit, val onEmptyClick: () -> Unit,

View file

@ -28,6 +28,7 @@ internal class SetTokenListTransformer(
private val isAccountsModeEnabled: Boolean, private val isAccountsModeEnabled: Boolean,
private val isRedesignEnabled: Boolean, private val isRedesignEnabled: Boolean,
private val isMultipleCardsEnabled: Boolean, private val isMultipleCardsEnabled: Boolean,
private val isPolymarketEnabled: Boolean,
) : WalletStateTransformer(userWallet.walletId) { ) : WalletStateTransformer(userWallet.walletId) {
private val tangemPayConverter by lazy { private val tangemPayConverter by lazy {
@ -165,6 +166,7 @@ internal class SetTokenListTransformer(
shouldShowMainPromo = shouldShowMainPromo, shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = isAccountsModeEnabled, isAccountsModeEnabled = isAccountsModeEnabled,
expandedAccounts = params.expandedAccounts, expandedAccounts = params.expandedAccounts,
isPolymarketEnabled = isPolymarketEnabled,
).convert(value = params.accountList) ).convert(value = params.accountList)
} }
} }

View file

@ -1,13 +1,20 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import com.tangem.core.ui.components.account.AccountIconSize
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.button.TangemButtonShape import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.button.TangemButtonType import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.button.TangemButtonUM import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
@ -38,6 +45,7 @@ internal class WalletTokensListUMConverter(
private val isAccountsModeEnabled: Boolean, private val isAccountsModeEnabled: Boolean,
private val expandedAccounts: Set<AccountId>, private val expandedAccounts: Set<AccountId>,
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>, private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability>,
private val isPolymarketEnabled: Boolean,
shouldShowMainPromo: Boolean, shouldShowMainPromo: Boolean,
) : Converter<AccountStatusList, WalletTokensListUM> { ) : Converter<AccountStatusList, WalletTokensListUM> {
@ -100,13 +108,52 @@ internal class WalletTokensListUMConverter(
} }
}.toPersistentList() }.toPersistentList()
val tokenListWithEntryPoints = if (isPolymarketEnabled) {
tokenListUM.add(index = 0, element = TokensListItemUM2.Prediction(tokenRowUM = getPredictionRow()))
} else {
tokenListUM
}
WalletTokensListUM.Content( WalletTokensListUM.Content(
tokenList = tokenListUM, tokenList = tokenListWithEntryPoints,
organizeButtonUM = getOrganizeButtonUM(value), organizeButtonUM = getOrganizeButtonUM(value),
) )
} }
} }
private fun getPredictionRow(): TangemTokenRowUM {
return TangemTokenRowUM.Content(
id = PREDICTION_ROW_ID,
// TODO([REDACTED_TASK_KEY]): replace the placeholder icon with the final Polymarket account asset
headIconUM = TangemIconUM.Currency(
currencyIconState = CurrencyIconState.CryptoPortfolio.Icon(
resId = R.drawable.ic_analytics_up_24,
color = PREDICTION_ICON_COLOR,
isGrayscale = false,
size = AccountIconSize.ExtraSmall,
),
),
titleUM = TangemTokenRowUM.TitleUM.Content(
text = resourceReference(R.string.prediction_account_title),
),
subtitleUM = TangemTokenRowUM.SubtitleUM.Content(
text = resourceReference(R.string.prediction_account_subtitle),
),
topEndContentUM = TangemTokenRowUM.EndContentUM.Content(
text = BigDecimal.ZERO.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
)
},
),
bottomEndContentUM = TangemTokenRowUM.EndContentUM.Empty,
onItemClick = { clickIntents.onPredictionAccountClick(selectedWallet.walletId) },
onItemLongClick = null,
)
}
private fun getTokenListItems( private fun getTokenListItems(
accountStatus: AccountStatus.CryptoPortfolio, accountStatus: AccountStatus.CryptoPortfolio,
promoCryptoCurrency: CryptoCurrencyStatus?, promoCryptoCurrency: CryptoCurrencyStatus?,
@ -185,4 +232,10 @@ internal class WalletTokensListUMConverter(
null null
} }
} }
@Suppress("MagicNumber")
private companion object {
const val PREDICTION_ROW_ID = "polymarket_prediction_account"
val PREDICTION_ICON_COLOR = Color(0xFF5A5AF0)
}
} }

View file

@ -12,6 +12,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoU
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.polymarket.api.PolymarketFeatureToggles
import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.utils.coroutines.combine7 import com.tangem.utils.coroutines.combine7
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
@ -39,6 +40,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val designFeatureToggles: DesignFeatureToggles, private val designFeatureToggles: DesignFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val polymarketFeatureToggles: PolymarketFeatureToggles,
) : BasicAccountListSubscriber() { ) : BasicAccountListSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> { override fun create(coroutineScope: CoroutineScope): Flow<*> {
@ -93,6 +95,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
stakingAvailabilityMap = stakingAvailabilityMap, stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo, shouldShowMainPromo = shouldShowMainPromo,
isMultipleCardsEnabled = tangemPayFeatureToggles.isMultipleCardsEnabled, isMultipleCardsEnabled = tangemPayFeatureToggles.isMultipleCardsEnabled,
isPolymarketEnabled = polymarketFeatureToggles.isPolymarketEnabled,
) )
} else { } else {
updateState( updateState(

View file

@ -91,6 +91,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
expandedAccounts: Set<AccountId>, expandedAccounts: Set<AccountId>,
isAccountMode: Boolean, isAccountMode: Boolean,
isMultipleCardsEnabled: Boolean, isMultipleCardsEnabled: Boolean,
isPolymarketEnabled: Boolean = false,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(), yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(), stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean = false, shouldShowMainPromo: Boolean = false,
@ -107,6 +108,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
isAccountsModeEnabled = isAccountMode, isAccountsModeEnabled = isAccountMode,
isRedesignEnabled = true, isRedesignEnabled = true,
isMultipleCardsEnabled = isMultipleCardsEnabled, isMultipleCardsEnabled = isMultipleCardsEnabled,
isPolymarketEnabled = isPolymarketEnabled,
), ),
) )
} }
@ -171,6 +173,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
isAccountsModeEnabled = false, isAccountsModeEnabled = false,
isRedesignEnabled = false, isRedesignEnabled = false,
isMultipleCardsEnabled = false, isMultipleCardsEnabled = false,
isPolymarketEnabled = false,
), ),
) )
} }

View file

@ -123,6 +123,12 @@ internal fun LazyListScope.tokensListItems2(
isBalanceHidden = isBalanceHidden, isBalanceHidden = isBalanceHidden,
modifier = modifier, modifier = modifier,
) )
is TokensListItemUM2.Prediction -> predictionItem(
listItem = listItem,
index = index,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
)
is TokensListItemUM2.Portfolio -> portfolioItem( is TokensListItemUM2.Portfolio -> portfolioItem(
listItem = listItem, listItem = listItem,
index = index, index = index,
@ -183,6 +189,41 @@ private fun LazyListScope.tokenItem(
} }
} }
private fun LazyListScope.predictionItem(
listItem: TokensListItemUM2.Prediction,
index: Int,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
item(
key = listItem.tokenRowUM.id,
contentType = listItem.tokenRowUM::class.java,
) {
val tokenRowUM = listItem.tokenRowUM
val itemModifier = modifier
.testTag(MainScreenTestTags.ACCOUNT_LIST_ITEM)
.semantics { lazyListItemPosition = index }
.padding(top = if (index == 0) TangemTheme.dimens2.x3 else TangemTheme.dimens2.x2)
// Standalone fully-rounded card, matching the account rows above/below it.
.roundedShapeItemDecoration(
radius = TangemTheme.dimens2.x5,
currentIndex = 0,
addDefaultPadding = false,
lastIndex = 0,
backgroundColor = TangemTheme.colors2.surface.level3,
)
.combinedClickable(
enabled = tokenRowUM.onItemClick != null,
onClick = tokenRowUM.onItemClick ?: {},
)
TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
modifier = itemModifier,
)
}
}
@Suppress("LongMethod") @Suppress("LongMethod")
private fun LazyListScope.portfolioItem( private fun LazyListScope.portfolioItem(
listItem: TokensListItemUM2.Portfolio, listItem: TokensListItemUM2.Portfolio,

View file

@ -75,6 +75,7 @@ class SetTokenListTransformerTest {
isAccountsModeEnabled = false, isAccountsModeEnabled = false,
isRedesignEnabled = true, isRedesignEnabled = true,
isMultipleCardsEnabled = false, isMultipleCardsEnabled = false,
isPolymarketEnabled = false,
) )
} }

View file

@ -1,6 +1,8 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.CardTypesResolver import com.tangem.domain.card.CardTypesResolver
@ -20,12 +22,17 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
@ -59,15 +66,65 @@ internal class WalletTokensListUMConverterTest {
assertThat(result.organizeButtonUM != null).isEqualTo(model.expectedButtonShown) assertThat(result.organizeButtonUM != null).isEqualTo(model.expectedButtonShown)
} }
private fun createConverter(selectedWallet: UserWallet): WalletTokensListUMConverter = WalletTokensListUMConverter( @Test
fun `GIVEN polymarket enabled WHEN convert THEN prediction row is first with expected content and click`() {
// Arrange
val clickIntents = mockk<WalletClickIntents>(relaxed = true)
val selectedWallet = mockColdWallet(isSingleCurrency = false, isSingleWalletWithToken = false)
every { selectedWallet.walletId } returns userWalletId
val converter = createConverter(
selectedWallet = selectedWallet,
isPolymarketEnabled = true,
clickIntents = clickIntents,
)
// Act
val result = converter.convert(value = nonEmptyAccountList()) as WalletTokensListUM.Content
val firstItem = result.tokenList.first()
// Assert
assertThat(firstItem).isInstanceOf(TokensListItemUM2.Prediction::class.java)
val row = (firstItem as TokensListItemUM2.Prediction).tokenRowUM as TangemTokenRowUM.Content
assertThat(row.id).isEqualTo("polymarket_prediction_account")
assertThat(row.titleUM).isEqualTo(
TangemTokenRowUM.TitleUM.Content(text = resourceReference(R.string.prediction_account_title)),
)
assertThat(row.subtitleUM).isEqualTo(
TangemTokenRowUM.SubtitleUM.Content(text = resourceReference(R.string.prediction_account_subtitle)),
)
row.onItemClick?.invoke()
verify(exactly = 1) { clickIntents.onPredictionAccountClick(userWalletId) }
}
@Test
fun `GIVEN polymarket disabled WHEN convert THEN no prediction row present`() {
// Arrange
val converter = createConverter(
selectedWallet = mockColdWallet(isSingleCurrency = false, isSingleWalletWithToken = false),
isPolymarketEnabled = false,
)
// Act
val result = converter.convert(value = nonEmptyAccountList()) as WalletTokensListUM.Content
// Assert
assertThat(result.tokenList.filterIsInstance<TokensListItemUM2.Prediction>()).isEmpty()
}
private fun createConverter(
selectedWallet: UserWallet,
isPolymarketEnabled: Boolean = false,
clickIntents: WalletClickIntents = mockk(relaxed = true),
): WalletTokensListUMConverter = WalletTokensListUMConverter(
appCurrency = AppCurrency.Default, appCurrency = AppCurrency.Default,
selectedWallet = selectedWallet, selectedWallet = selectedWallet,
clickIntents = mockk(relaxed = true), clickIntents = clickIntents,
yieldModuleApyMap = emptyMap(), yieldModuleApyMap = emptyMap(),
isAccountsModeEnabled = false, isAccountsModeEnabled = false,
expandedAccounts = emptySet(), expandedAccounts = emptySet(),
stakingAvailabilityMap = emptyMap(), stakingAvailabilityMap = emptyMap(),
shouldShowMainPromo = false, shouldShowMainPromo = false,
isPolymarketEnabled = isPolymarketEnabled,
) )
private fun mockColdWallet(isSingleCurrency: Boolean, isSingleWalletWithToken: Boolean): UserWallet.Cold { private fun mockColdWallet(isSingleCurrency: Boolean, isSingleWalletWithToken: Boolean): UserWallet.Cold {

View file

@ -367,6 +367,9 @@ include(":features:token-recieve:impl")
include(":features:yield-supply:api") include(":features:yield-supply:api")
include(":features:yield-supply:impl") include(":features:yield-supply:impl")
include(":features:polymarket:api")
include(":features:polymarket:impl")
include(":features:approval:api") include(":features:approval:api")
include(":features:approval:impl") include(":features:approval:impl")