From 23f864b18d9c2af1ea7a46a29fd3003e1e854236 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Jul 2026 14:51:10 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../tangem/tap/routing/utils/ChildFactory.kt | 9 +++ .../com/tangem/common/routing/AppRoute.kt | 5 ++ .../configs/feature_toggles_config.json | 4 ++ core/res/src/main/res/values/strings.xml | 2 + features/polymarket/api/build.gradle.kts | 21 +++++++ .../polymarket/api/PolymarketComponent.kt | 14 +++++ .../api/PolymarketFeatureToggles.kt | 5 ++ features/polymarket/impl/build.gradle.kts | 32 +++++++++++ .../impl/DefaultPolymarketComponent.kt | 35 ++++++++++++ .../impl/di/PolymarketBindsModule.kt | 32 +++++++++++ .../DefaultPolymarketFeatureToggles.kt | 13 +++++ features/wallet/impl/build.gradle.kts | 1 + .../model/intents/WalletClickIntents.kt | 5 ++ .../router/DefaultWalletRouter.kt | 4 ++ .../presentation/router/InnerWalletRouter.kt | 3 + .../wallet/state/model/WalletTokensListUM.kt | 9 +++ .../transformers/SetTokenListTransformer.kt | 2 + .../converter/WalletTokensListUMConverter.kt | 55 ++++++++++++++++++- .../subscribers/AccountListSubscriber.kt | 3 + .../subscribers/BasicAccountListSubscriber.kt | 3 + .../multicurrency/MultiCurrencyContent.kt | 41 ++++++++++++++ .../SetTokenListTransformerTest.kt | 1 + .../WalletTokensListUMConverterTest.kt | 1 + settings.gradle.kts | 3 + 25 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 features/polymarket/api/build.gradle.kts create mode 100644 features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketComponent.kt create mode 100644 features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketFeatureToggles.kt create mode 100644 features/polymarket/impl/build.gradle.kts create mode 100644 features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/DefaultPolymarketComponent.kt create mode 100644 features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/di/PolymarketBindsModule.kt create mode 100644 features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/featuretoggles/DefaultPolymarketFeatureToggles.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f63067f340..8da953b5fd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -342,6 +342,8 @@ dependencies { implementation(projects.features.tokenRecieve.impl) implementation(projects.features.yieldSupply.api) implementation(projects.features.yieldSupply.impl) + implementation(projects.features.polymarket.api) + implementation(projects.features.polymarket.impl) implementation(projects.features.approval.api) implementation(projects.features.approval.impl) implementation(projects.features.forYou.api) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index fc81a2e8cb..636d4a54d3 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -46,6 +46,7 @@ import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.virtualaccount.onboarding.component.VirtualAccountOnboardingComponent import com.tangem.features.wallet.WalletEntryComponent 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.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent 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 surveyComponentFactory: SurveyComponent.Factory, private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory, + private val polymarketComponentFactory: PolymarketComponent.Factory, private val feedEntryComponentFactory: FeedEntryComponent.Factory, private val addressBookComponentFactory: AddressBookComponent.Factory, ) { @@ -745,6 +747,13 @@ internal class ChildFactory @Inject constructor( componentFactory = yieldSupplyEntryComponentFactory, ) } + is AppRoute.Polymarket -> { + createComponentChild( + context = context, + params = PolymarketComponent.Params(userWalletId = route.userWalletId), + componentFactory = polymarketComponentFactory, + ) + } is AppRoute.NewsDetails -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index e65a45a9f9..f6ad2cf1b9 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -556,6 +556,11 @@ sealed class AppRoute(val path: String) : Route { val apy: String, ) : AppRoute(path = "/yield_supply_entry/${userWalletId.stringValue}/${cryptoCurrency.symbol}") + @Serializable + data class Polymarket( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/polymarket/${userWalletId.stringValue}") + @Serializable data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId") diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index ddaa0bab10..2360da168b 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -182,5 +182,9 @@ { "name": "TWI_1192_TANGEM_PAY_CASHBACK_ENABLED", "version": "undefined" + }, + { + "name": "AND_16204_POLYMARKET_ENABLED", + "version": "undefined" } ] diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 41593daacd..2fcc2f9e07 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -869,6 +869,8 @@ Buy or receive crypto to start using your wallet. Get your first crypto To begin tracking your crypto assets and transactions, add tokens + Add funds & start predict + Prediction account Manage tokens Scan QR code to send funds or connect to an app To access all the networks you need to scan the card diff --git a/features/polymarket/api/build.gradle.kts b/features/polymarket/api/build.gradle.kts new file mode 100644 index 0000000000..6950b44e85 --- /dev/null +++ b/features/polymarket/api/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketComponent.kt b/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketComponent.kt new file mode 100644 index 0000000000..3f0c3011a9 --- /dev/null +++ b/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketComponent.kt @@ -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 +} \ No newline at end of file diff --git a/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketFeatureToggles.kt b/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketFeatureToggles.kt new file mode 100644 index 0000000000..fe21e9a313 --- /dev/null +++ b/features/polymarket/api/src/main/java/com/tangem/features/polymarket/api/PolymarketFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.polymarket.api + +interface PolymarketFeatureToggles { + val isPolymarketEnabled: Boolean +} \ No newline at end of file diff --git a/features/polymarket/impl/build.gradle.kts b/features/polymarket/impl/build.gradle.kts new file mode 100644 index 0000000000..978b05e794 --- /dev/null +++ b/features/polymarket/impl/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/DefaultPolymarketComponent.kt b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/DefaultPolymarketComponent.kt new file mode 100644 index 0000000000..f0b8331ced --- /dev/null +++ b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/DefaultPolymarketComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/di/PolymarketBindsModule.kt b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/di/PolymarketBindsModule.kt new file mode 100644 index 0000000000..988b597952 --- /dev/null +++ b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/di/PolymarketBindsModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/featuretoggles/DefaultPolymarketFeatureToggles.kt b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/featuretoggles/DefaultPolymarketFeatureToggles.kt new file mode 100644 index 0000000000..bb93195d97 --- /dev/null +++ b/features/polymarket/impl/src/main/java/com/tangem/features/polymarket/impl/featuretoggles/DefaultPolymarketFeatureToggles.kt @@ -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) +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 16d3a14c52..01864cd7b5 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -135,6 +135,7 @@ dependencies { api(projects.features.feed.api) api(projects.features.hotWallet.api) api(projects.features.promoBanners.api) + api(projects.features.polymarket.api) api(projects.features.pushNotifications.api) api(projects.features.pushNotificationSettings.api) api(projects.features.send.api) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt index 0ed6aa49de..d4d224f084 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletClickIntents.kt @@ -134,6 +134,11 @@ internal class WalletClickIntents @Inject constructor( 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) { val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index c97cb0afee..ec0550f1e2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -131,6 +131,10 @@ internal class DefaultWalletRouter @Inject constructor( ) } + override fun openPolymarket(userWalletId: UserWalletId) { + router.push(AppRoute.Polymarket(userWalletId = userWalletId)) + } + override fun isWalletLastScreen(): Boolean { return router.stack.lastOrNull() is AppRoute.Wallet } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 5258923d61..750e18fb19 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -125,4 +125,7 @@ internal interface InnerWalletRouter { /** Open Transfer screen */ fun openTransfer(userWalletId: UserWalletId) + + /** Open Polymarket (prediction account) entry screen */ + fun openPolymarket(userWalletId: UserWalletId) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListUM.kt index 7d3d1f5dc5..53f38f012f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListUM.kt @@ -95,6 +95,15 @@ internal sealed interface TokensListItemUM2 { override val tokenRowUM: TangemTokenRowUM, ) : 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( override val tokenRowUM: TangemTokenRowUM, val onEmptyClick: () -> Unit, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 9baaa325c1..9332763963 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -28,6 +28,7 @@ internal class SetTokenListTransformer( private val isAccountsModeEnabled: Boolean, private val isRedesignEnabled: Boolean, private val isMultipleCardsEnabled: Boolean, + private val isPolymarketEnabled: Boolean, ) : WalletStateTransformer(userWallet.walletId) { private val tangemPayConverter by lazy { @@ -165,6 +166,7 @@ internal class SetTokenListTransformer( shouldShowMainPromo = shouldShowMainPromo, isAccountsModeEnabled = isAccountsModeEnabled, expandedAccounts = params.expandedAccounts, + isPolymarketEnabled = isPolymarketEnabled, ).convert(value = params.accountList) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt index 6727c0f20b..b255bceb3b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt @@ -1,13 +1,20 @@ 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.TangemButtonSize import com.tangem.core.ui.ds.button.TangemButtonType import com.tangem.core.ui.ds.button.TangemButtonUM import com.tangem.core.ui.ds.image.TangemIconUM 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.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.domain.account.models.AccountStatusList import com.tangem.domain.appcurrency.model.AppCurrency @@ -38,6 +45,7 @@ internal class WalletTokensListUMConverter( private val isAccountsModeEnabled: Boolean, private val expandedAccounts: Set, private val stakingAvailabilityMap: Map, + private val isPolymarketEnabled: Boolean, shouldShowMainPromo: Boolean, ) : Converter { @@ -100,13 +108,52 @@ internal class WalletTokensListUMConverter( } }.toPersistentList() + val tokenListWithEntryPoints = if (isPolymarketEnabled) { + tokenListUM.add(index = 0, element = TokensListItemUM2.Prediction(tokenRowUM = getPredictionRow())) + } else { + tokenListUM + } + WalletTokensListUM.Content( - tokenList = tokenListUM, + tokenList = tokenListWithEntryPoints, 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( accountStatus: AccountStatus.CryptoPortfolio, promoCryptoCurrency: CryptoCurrencyStatus?, @@ -185,4 +232,10 @@ internal class WalletTokensListUMConverter( null } } + + @Suppress("MagicNumber") + private companion object { + const val PREDICTION_ROW_ID = "polymarket_prediction_account" + val PREDICTION_ICON_COLOR = Color(0xFF5A5AF0) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index f98a5d1508..d7fc0ea146 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -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.presentation.account.AccountDependencies 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.utils.coroutines.combine7 import com.tangem.utils.logging.TangemLogger @@ -39,6 +40,7 @@ internal class AccountListSubscriber @AssistedInject constructor( private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val designFeatureToggles: DesignFeatureToggles, private val tangemPayFeatureToggles: TangemPayFeatureToggles, + private val polymarketFeatureToggles: PolymarketFeatureToggles, ) : BasicAccountListSubscriber() { override fun create(coroutineScope: CoroutineScope): Flow<*> { @@ -93,6 +95,7 @@ internal class AccountListSubscriber @AssistedInject constructor( stakingAvailabilityMap = stakingAvailabilityMap, shouldShowMainPromo = shouldShowMainPromo, isMultipleCardsEnabled = tangemPayFeatureToggles.isMultipleCardsEnabled, + isPolymarketEnabled = polymarketFeatureToggles.isPolymarketEnabled, ) } else { updateState( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index 1f71b4a6fb..a066ca4361 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -91,6 +91,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { expandedAccounts: Set, isAccountMode: Boolean, isMultipleCardsEnabled: Boolean, + isPolymarketEnabled: Boolean = false, yieldSupplyApyMap: Map = emptyMap(), stakingAvailabilityMap: Map = emptyMap(), shouldShowMainPromo: Boolean = false, @@ -107,6 +108,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { isAccountsModeEnabled = isAccountMode, isRedesignEnabled = true, isMultipleCardsEnabled = isMultipleCardsEnabled, + isPolymarketEnabled = isPolymarketEnabled, ), ) } @@ -171,6 +173,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { isAccountsModeEnabled = false, isRedesignEnabled = false, isMultipleCardsEnabled = false, + isPolymarketEnabled = false, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index c2a634a11c..e8ce6bb945 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -123,6 +123,12 @@ internal fun LazyListScope.tokensListItems2( isBalanceHidden = isBalanceHidden, modifier = modifier, ) + is TokensListItemUM2.Prediction -> predictionItem( + listItem = listItem, + index = index, + isBalanceHidden = isBalanceHidden, + modifier = modifier, + ) is TokensListItemUM2.Portfolio -> portfolioItem( listItem = listItem, 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") private fun LazyListScope.portfolioItem( listItem: TokensListItemUM2.Portfolio, diff --git a/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformerTest.kt b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformerTest.kt index c2d1e1f38a..46b1ce5f3e 100644 --- a/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformerTest.kt +++ b/features/wallet/impl/src/test/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformerTest.kt @@ -75,6 +75,7 @@ class SetTokenListTransformerTest { isAccountsModeEnabled = false, isRedesignEnabled = true, isMultipleCardsEnabled = false, + isPolymarketEnabled = false, ) } diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt index 8ea472e436..aa2275563c 100644 --- a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt +++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt @@ -68,6 +68,7 @@ internal class WalletTokensListUMConverterTest { expandedAccounts = emptySet(), stakingAvailabilityMap = emptyMap(), shouldShowMainPromo = false, + isPolymarketEnabled = false, ) private fun mockColdWallet(isSingleCurrency: Boolean, isSingleWalletWithToken: Boolean): UserWallet.Cold { diff --git a/settings.gradle.kts b/settings.gradle.kts index ab7057d400..deee71855b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -367,6 +367,9 @@ include(":features:token-recieve:impl") include(":features:yield-supply:api") include(":features:yield-supply:impl") +include(":features:polymarket:api") +include(":features:polymarket:impl") + include(":features:approval:api") include(":features:approval:impl") From 650b7716f16ae85bb584d889b6aaff5c70747644 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 15 Jul 2026 19:02:13 +0300 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../WalletTokensListUMConverterTest.kt | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt index aa2275563c..8660a0fc63 100644 --- a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt +++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverterTest.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter 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.appcurrency.model.AppCurrency 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.wallet.UserWallet 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 io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic +import io.mockk.verify import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource @@ -59,16 +66,65 @@ internal class WalletTokensListUMConverterTest { 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(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()).isEmpty() + } + + private fun createConverter( + selectedWallet: UserWallet, + isPolymarketEnabled: Boolean = false, + clickIntents: WalletClickIntents = mockk(relaxed = true), + ): WalletTokensListUMConverter = WalletTokensListUMConverter( appCurrency = AppCurrency.Default, selectedWallet = selectedWallet, - clickIntents = mockk(relaxed = true), + clickIntents = clickIntents, yieldModuleApyMap = emptyMap(), isAccountsModeEnabled = false, expandedAccounts = emptySet(), stakingAvailabilityMap = emptyMap(), shouldShowMainPromo = false, - isPolymarketEnabled = false, + isPolymarketEnabled = isPolymarketEnabled, ) private fun mockColdWallet(isSingleCurrency: Boolean, isSingleWalletWithToken: Boolean): UserWallet.Cold {