Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-16 14:43:55 +05:00
parent bc12ef112d
commit a887727d6a
25 changed files with 136 additions and 103 deletions

View file

@ -146,5 +146,9 @@
{
"name": "AND_15489_EXPRESS_SHARE_BUTTON_ENABLED",
"version": "undefined"
},
{
"name": "AND_15235_VISA_MULTIPLE_CARDS",
"version": "5.40"
}
]

View file

@ -274,6 +274,16 @@ sealed class TangemPayAnalyticsEvents(
event = "Visa Add Extra Card Clicked",
)
class FakeDoorPopupDisplayed : TangemPayAnalyticsEvents(
categoryName = "Visa Card Management",
event = "Visa Fakedoor Popup Displayed",
)
class FakeDoorGotitClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Card Management",
event = "Visa Fakedoor Gotit Clicked",
)
class IssueAdditionalCardPopupShown : TangemPayAnalyticsEvents(
categoryName = "Visa Card Management",
event = "Visa Extra Card Issuance Popup Displayed",

View file

@ -4,4 +4,5 @@ interface TangemPayFeatureToggles {
val isRedesignEnabled: Boolean
val isCloseCardEnabled: Boolean
val isRemoveAccountEnabled: Boolean
val isMultipleCardsEnabled: Boolean
}

View file

@ -15,4 +15,7 @@ internal class DefaultTangemPayFeatureToggles(
override val isRemoveAccountEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15741_VISA_PAY_REMOVE_ACCOUNT)
override val isMultipleCardsEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15235_VISA_MULTIPLE_CARDS)
}

View file

@ -16,6 +16,7 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
@ -72,9 +73,12 @@ internal class DefaultTangemPayDetailsContainerComponent @AssistedInject constru
cardId = config.cardId,
),
)
TangemPayAccountDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
is TangemPayAccountDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
params = params,
params = TangemPayAddToWalletComponent.Params(
card = config.card,
userWalletId = params.initialStatus.userWalletId,
),
)
}

View file

@ -10,13 +10,15 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen
import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreenV2
internal class TangemPayAddToWalletComponent(
private val appComponentContext: AppComponentContext,
private val params: TangemPayDetailsContainerComponent.Params,
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: TangemPayAddToWalletModel = getOrCreateModel(params)
@ -40,4 +42,6 @@ internal class TangemPayAddToWalletComponent(
}
}
}
data class Params(val card: TangemPayCard, val userWalletId: UserWalletId)
}

View file

@ -21,6 +21,7 @@ import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.limit.setup.TangemPayCardLimitSetupComponent
import com.tangem.features.tangempay.limit.setup.TangemPayCardLimitSetupSuccessComponent
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.features.tokenreceive.TokenReceiveComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -80,20 +81,26 @@ internal class TangemPayCardPageComponent @AssistedInject constructor(
),
isRedesignEnabled = tangemPayFeatureToggles.isRedesignEnabled,
)
TangemPayCardDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
is TangemPayCardDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
params = TangemPayDetailsContainerComponent.Params(initialStatus = params.initialStatus),
params = TangemPayAddToWalletComponent.Params(
card = config.card,
userWalletId = params.initialStatus.userWalletId,
),
)
is TangemPayCardDetailsInnerRoute.EditCardDisplayName -> TangemPayEditDisplayNameComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
params = TangemPayCardScopedParams(
initialStatus = params.initialStatus,
cardId = config.cardId,
params = TangemPayEditDisplayNameComponent.Params(
card = config.card,
userWalletId = params.initialStatus.userWalletId,
),
)
TangemPayCardDetailsInnerRoute.LimitSetup -> TangemPayCardLimitSetupComponent(
is TangemPayCardDetailsInnerRoute.LimitSetup -> TangemPayCardLimitSetupComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
params = TangemPayDetailsContainerComponent.Params(initialStatus = params.initialStatus),
params = TangemPayCardLimitSetupComponent.Params(
card = config.card,
userWalletId = params.initialStatus.userWalletId,
),
)
TangemPayCardDetailsInnerRoute.LimitSetupSuccess -> TangemPayCardLimitSetupSuccessComponent(
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),

View file

@ -10,13 +10,15 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.features.tangempay.entity.DisplayNameState
import com.tangem.features.tangempay.model.TangemPayEditDisplayNameModel
import com.tangem.features.tangempay.ui.TangemPayEditDisplayNameScreen
internal class TangemPayEditDisplayNameComponent(
private val appComponentContext: AppComponentContext,
params: TangemPayCardScopedParams,
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: TangemPayEditDisplayNameModel = getOrCreateModel(params)
@ -45,4 +47,6 @@ internal class TangemPayEditDisplayNameComponent(
)
}
}
data class Params(val card: TangemPayCard, val userWalletId: UserWalletId)
}

View file

@ -8,11 +8,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.domain.models.pay.TangemPayCard
import com.tangem.domain.models.wallet.UserWalletId
internal class TangemPayCardLimitSetupComponent(
appComponentContext: AppComponentContext,
params: TangemPayDetailsContainerComponent.Params,
params: Params,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: TangemPayCardLimitSetupModel = getOrCreateModel(params)
@ -33,4 +34,6 @@ internal class TangemPayCardLimitSetupComponent(
)
}
}
data class Params(val card: TangemPayCard, val userWalletId: UserWalletId)
}

View file

@ -23,11 +23,8 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
@ -51,9 +48,9 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
private val featureToggles: TangemPayFeatureToggles,
) : Model() {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
private val cardId: String = params.initialStatus.firstCard().id
private val userWalletId = params.initialStatus.userWalletId
private val params: TangemPayCardLimitSetupComponent.Params = paramsContainer.require()
private val cardId: String = params.card.id
private val userWalletId = params.userWalletId
private var currentAdminLimit: BigDecimal? = null
val uiState: StateFlow<TangemPayCardLimitSetupUM>

View file

@ -7,15 +7,13 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.components.TangemPayAddToWalletComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayAddToWalletStepItemUM
import com.tangem.features.tangempay.entity.TangemPayAddToWalletUM
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
import com.tangem.features.tangempay.model.controller.TangemPayCardDetailsController
import com.tangem.features.tangempay.utils.GoogleWalletUtil
import com.tangem.features.tangempay.utils.firstCard
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
@ -33,12 +31,12 @@ internal class TangemPayAddToWalletModel @Inject constructor(
cardDetailsControllerFactory: TangemPayCardDetailsController.Factory,
) : Model() {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
private val params: TangemPayAddToWalletComponent.Params = paramsContainer.require()
private val cardDetailsController = cardDetailsControllerFactory.create(
scope = modelScope,
initialCard = params.initialStatus.firstCard(),
userWalletId = params.initialStatus.userWalletId,
card = params.card,
userWalletId = params.userWalletId,
config = TangemPayCardDetailsController.Config(
isEditingNameEnabled = false,
shouldShowCardDetailsButtonOnCard = true,

View file

@ -112,9 +112,7 @@ internal class TangemPayCardPageModel @Inject constructor(
private val cryptoCurrency
get() = currentStatus.value.cryptoCurrency
// Multiple cards are temporarily gated behind the same toggle as close-card.
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isCloseCardEnabled
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isMultipleCardsEnabled
val uiState: StateFlow<TangemPayCardPageUM>
field = MutableStateFlow(
@ -203,13 +201,13 @@ internal class TangemPayCardPageModel @Inject constructor(
cards.forEach { card ->
ordered[card.id] = cardControllers[card.id] ?: cardDetailsControllerFactory.create(
scope = childCardScope(),
initialCard = card,
card = card,
userWalletId = userWalletId,
config = TangemPayCardDetailsController.Config(
isEditingNameEnabled = true,
shouldShowCardDetailsButtonOnCard = false,
),
onEditNameClick = { router.push(TangemPayCardDetailsInnerRoute.EditCardDisplayName(card.id)) },
onEditNameClick = { router.push(TangemPayCardDetailsInnerRoute.EditCardDisplayName(card)) },
)
}
cardControllers.clear()
@ -378,8 +376,9 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private fun onClickLimitChange() {
val card = selectedCard() ?: return
analytics.send(TangemPayAnalyticsEvents.LimitChangeClicked())
router.push(TangemPayCardDetailsInnerRoute.LimitSetup)
router.push(TangemPayCardDetailsInnerRoute.LimitSetup(card))
}
private fun onClickChangePIN(isPinSet: Boolean) {
@ -542,7 +541,8 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private fun onClickAddToWallet() {
router.push(TangemPayCardDetailsInnerRoute.AddToWallet)
val card = selectedCard() ?: return
router.push(TangemPayCardDetailsInnerRoute.AddToWallet(card))
}
private fun onClickCloseBanner() {

View file

@ -99,8 +99,7 @@ internal class TangemPayDetailsModel @Inject constructor(
val cryptoCurrency
get() = currentStatus.value.cryptoCurrency
// Multiple cards are temporarily gated behind the same toggle as close-card.
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isCloseCardEnabled
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isMultipleCardsEnabled
private val stateFactory = TangemPayDetailsStateFactory(
onBack = router::pop,
@ -279,7 +278,8 @@ internal class TangemPayDetailsModel @Inject constructor(
private fun onClickAddToWalletBlock() {
analytics.send(TangemPayAnalyticsEvents.AddToWalletClicked())
router.push(TangemPayAccountDetailsInnerRoute.AddToWallet)
val card = currentStatus.value.ifLoadedOrNull { it.cards.firstOrNull() } ?: return
router.push(TangemPayAccountDetailsInnerRoute.AddToWallet(card))
}
private fun onClickCloseAddToWalletBlock() {
@ -367,14 +367,19 @@ internal class TangemPayDetailsModel @Inject constructor(
override fun onAddCardClick() {
analytics.send(TangemPayAnalyticsEvents.AddExtraCardClicked())
if (!isMultipleCardsEnabled) {
uiMessageSender.send(TangemPayMessagesFactory.createFutureFeature(onGotItClick = {}))
analytics.send(TangemPayAnalyticsEvents.FakeDoorPopupDisplayed())
uiMessageSender.send(
TangemPayMessagesFactory.createFutureFeature(
onGotItClick = {
analytics.send(TangemPayAnalyticsEvents.FakeDoorGotitClicked())
},
),
)
return
}
val activeCardsCount = currentStatus.value.ifLoadedOrNull { loaded ->
loaded.cards.count { it.cardStatus.isActive }
} ?: 0
if (activeCardsCount >= MAX_ACTIVE_CARDS) {
uiMessageSender.send(TangemPayMessagesFactory.createMaximumCardsIssued(maxCards = MAX_ACTIVE_CARDS))
val activeCardsCount = currentStatus.value.ifLoadedOrNull { loaded -> loaded.cards.count() } ?: 0
if (activeCardsCount >= ALLOWED_MAX_CARDS_COUNT) {
uiMessageSender.send(TangemPayMessagesFactory.createMaximumCardsIssued(maxCards = ALLOWED_MAX_CARDS_COUNT))
return
}
modelScope.launch {
@ -459,6 +464,6 @@ internal class TangemPayDetailsModel @Inject constructor(
}
private companion object {
const val MAX_ACTIVE_CARDS = 3
const val ALLOWED_MAX_CARDS_COUNT = 3
}
}

View file

@ -19,13 +19,11 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.usecase.UpdateTangemPayCardNameUseCase
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.TangemPayCardScopedParams
import com.tangem.features.tangempay.components.TangemPayEditDisplayNameComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
import com.tangem.features.tangempay.entity.TangemPayEditDisplayNameUM
import com.tangem.features.tangempay.model.controller.TangemPayCardDetailsController
import com.tangem.features.tangempay.utils.requireLoaded
import com.tangem.features.tangempay.utils.userWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -45,15 +43,15 @@ internal class TangemPayEditDisplayNameModel @Inject constructor(
cardDetailsControllerFactory: TangemPayCardDetailsController.Factory,
) : Model() {
private val params: TangemPayCardScopedParams = paramsContainer.require()
private val params: TangemPayEditDisplayNameComponent.Params = paramsContainer.require()
private val card = params.initialStatus.requireLoaded().requireCardWithId(params.cardId)
private val card = params.card
private val originalDisplayName = card.displayName?.value.orEmpty()
private val cardDetailsController = cardDetailsControllerFactory.create(
scope = modelScope,
initialCard = card,
userWalletId = params.initialStatus.userWalletId,
card = card,
userWalletId = params.userWalletId,
config = TangemPayCardDetailsController.Config(
isEditingNameEnabled = false,
shouldShowCardDetailsButtonOnCard = false,
@ -79,7 +77,7 @@ internal class TangemPayEditDisplayNameModel @Inject constructor(
)
init {
subscribeToCardNameChanges(card.id, params.initialStatus.userWalletId)
subscribeToCardNameChanges(card.id, params.userWalletId)
}
fun isRedesignEnabled() = featureToggles.isRedesignEnabled
@ -126,7 +124,7 @@ internal class TangemPayEditDisplayNameModel @Inject constructor(
uiState.update { it.copy(isLoading = true) }
updateCardNameUseCase(
cardId = card.id,
userWalletId = params.initialStatus.userWalletId,
userWalletId = params.userWalletId,
displayName = cardDisplayName,
).onRight {
router.pop()

View file

@ -33,12 +33,8 @@ import com.tangem.utils.transformer.update
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import com.tangem.utils.transformer.update as transformerUpdate
private const val SHOW_DETAILS_TIME = 30_000L
@ -57,7 +53,7 @@ private const val SHOW_DETAILS_TIME = 30_000L
@Stable
internal class TangemPayCardDetailsController @AssistedInject constructor(
@Assisted private val scope: CoroutineScope,
@Assisted private val initialCard: TangemPayCard,
@Assisted private val card: TangemPayCard,
@Assisted private val userWalletId: UserWalletId,
@Assisted private val config: Config,
@Assisted private val onEditNameClick: () -> Unit,
@ -69,13 +65,13 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
) {
val cardId: String = initialCard.id
val cardId: String = card.id
private var frozenStateJob: Job? = null
private val stateFactory = TangemPayCardDetailsBlockStateFactory(
cardNumberEnd = initialCard.lastDigits,
displayName = initialCard.displayName,
cardNumberEnd = card.lastDigits,
displayName = card.displayName,
isEditingNameEnabled = config.isEditingNameEnabled,
onEditNameClick = onEditNameClick,
onReveal = ::requestReveal,
@ -219,7 +215,7 @@ internal class TangemPayCardDetailsController @AssistedInject constructor(
interface Factory {
fun create(
scope: CoroutineScope,
initialCard: TangemPayCard,
card: TangemPayCard,
userWalletId: UserWalletId,
config: Config,
onEditNameClick: () -> Unit,

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.navigation
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.pay.TangemPayCard
import kotlinx.serialization.Serializable
@Serializable
@ -12,5 +13,5 @@ internal sealed class TangemPayAccountDetailsInnerRoute : Route {
data class CardDetails(val cardId: String) : TangemPayAccountDetailsInnerRoute()
@Serializable
data object AddToWallet : TangemPayAccountDetailsInnerRoute()
data class AddToWallet(val card: TangemPayCard) : TangemPayAccountDetailsInnerRoute()
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.navigation
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.models.pay.TangemPayCard
import kotlinx.serialization.Serializable
@Serializable
@ -16,13 +17,13 @@ internal sealed class TangemPayCardDetailsInnerRoute : Route {
data object ChangePINSuccess : TangemPayCardDetailsInnerRoute()
@Serializable
data object AddToWallet : TangemPayCardDetailsInnerRoute()
data class AddToWallet(val card: TangemPayCard) : TangemPayCardDetailsInnerRoute()
@Serializable
data class EditCardDisplayName(val cardId: String) : TangemPayCardDetailsInnerRoute()
data class EditCardDisplayName(val card: TangemPayCard) : TangemPayCardDetailsInnerRoute()
@Serializable
data object LimitSetup : TangemPayCardDetailsInnerRoute()
data class LimitSetup(val card: TangemPayCard) : TangemPayCardDetailsInnerRoute()
@Serializable
data object LimitSetupSuccess : TangemPayCardDetailsInnerRoute()

View file

@ -6,7 +6,6 @@ import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.pay.*
@ -15,7 +14,6 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.every
import io.mockk.mockk
@ -52,14 +50,7 @@ internal class TangemPayCardLimitSetupModelTest {
state = TangemPayCardState.Active,
)
private val initialStatus: AccountStatus.Payment = AccountStatus.Payment(
account = Account.Payment(userWalletId = userWalletId),
value = mockk<PaymentAccountStatusValue.Loaded>(relaxed = true) {
every { cards } returns listOf(initialCard)
},
)
private val params = TangemPayDetailsContainerComponent.Params(initialStatus = initialStatus)
private val params = TangemPayCardLimitSetupComponent.Params(card = initialCard, userWalletId = userWalletId)
private fun createModel(
adminLimit: BigDecimal? = BigDecimal("1000"),

View file

@ -4,10 +4,10 @@ import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth.assertThat
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.error.UniversalError
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.CardDisplayName
@ -24,19 +24,11 @@ import com.tangem.features.tangempay.entity.CardDataType
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
import com.tangem.features.tangempay.model.listener.DefaultCardDetailsEventListener
import com.tangem.utils.StringsSigns
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.mockk.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.Test
private const val SHOW_DETAILS_TIME = 30_000L
@ -219,7 +211,7 @@ internal class TangemPayCardDetailsControllerTest {
onEditNameClick: () -> Unit = {},
): TangemPayCardDetailsController = TangemPayCardDetailsController(
scope = scope,
initialCard = card,
card = card,
userWalletId = userWalletId,
config = config,
onEditNameClick = onEditNameClick,

View file

@ -28,12 +28,14 @@ internal class SetTokenListTransformer(
private val isAccountsModeEnabled: Boolean,
private val isRedesignEnabled: Boolean,
private val isAddAndManageTokensEnabled: Boolean,
private val isMultipleCardsEnabled: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
private val tangemPayConverter by lazy {
TangemPayMainBlockConverter(
tangemPayClickIntents = clickIntents,
isRedesignEnabled = isRedesignEnabled,
isMultipleCardsEnabled = isMultipleCardsEnabled,
)
}

View file

@ -2,9 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import androidx.compose.ui.text.SpanStyle
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.formatStyled
@ -23,6 +21,7 @@ import java.util.Currency
internal class TangemPayMainBlockConverter(
private val tangemPayClickIntents: TangemPayIntents,
private val isRedesignEnabled: Boolean,
private val isMultipleCardsEnabled: Boolean,
) : Converter<AccountStatus.Payment, TangemPayMainUM> {
@Suppress("LongMethod", "CyclomaticComplexMethod")
override fun convert(value: AccountStatus.Payment): TangemPayMainUM {
@ -66,13 +65,19 @@ internal class TangemPayMainBlockConverter(
onClick = { tangemPayClickIntents.openDetails(value) },
)
is PaymentAccountStatusValue.Loaded -> {
val cardsCount = statusValue.cards.count()
val card = statusValue.cards.firstOrNull() ?: return TangemPayMainUM.TemporaryUnavailable
val subtitle = when {
isMultipleCardsEnabled -> pluralReference(
id = R.plurals.tangempay_cards_count,
count = cardsCount,
formatArgs = wrappedList(cardsCount),
)
card.state == TangemPayCardState.Reissuing -> resourceReference(R.string.tangempay_status_replacing)
else -> stringReference("*${card.lastDigits}")
}
TangemPayMainUM.Content(
subtitle = if (card.state == TangemPayCardState.Reissuing) {
resourceReference(R.string.tangempay_status_replacing)
} else {
stringReference("*${card.lastDigits}")
},
subtitle = subtitle,
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
balance = getBalanceText(
currencyCode = statusValue.balance.fiatBalance.currency,

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.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.utils.coroutines.combine7
import com.tangem.utils.logging.TangemLogger
@ -19,12 +20,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.*
import java.math.BigDecimal
/**
@ -44,6 +40,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val walletFeatureToggles: WalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) : BasicAccountListSubscriber() {
override val isAddAndManageTokensEnabled: Boolean
@ -100,6 +97,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
isMultipleCardsEnabled = tangemPayFeatureToggles.isMultipleCardsEnabled,
)
} else {
updateState(

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.common.ui.tokens.TokenConverterParams
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -7,6 +8,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.staking.model.StakingAvailability
@ -16,8 +18,6 @@ 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.transformers.SetTokenListErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
import com.tangem.common.ui.tokens.TokenConverterParams
import com.tangem.domain.models.account.AccountStatus
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
@ -91,6 +91,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
appCurrency: AppCurrency,
expandedAccounts: Set<AccountId>,
isAccountMode: Boolean,
isMultipleCardsEnabled: Boolean,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean = false,
@ -107,6 +108,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
isAccountsModeEnabled = isAccountMode,
isRedesignEnabled = true,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
isMultipleCardsEnabled = isMultipleCardsEnabled,
),
)
}
@ -171,6 +173,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
isAccountsModeEnabled = false,
isRedesignEnabled = false,
isAddAndManageTokensEnabled = isAddAndManageTokensEnabled,
isMultipleCardsEnabled = false,
),
)
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWallet
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.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -12,7 +13,9 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
@Suppress("LongParameterList")
internal class SingleWalletSubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet.Cold,
override val accountDependencies: AccountDependencies,
@ -20,6 +23,7 @@ internal class SingleWalletSubscriber @AssistedInject constructor(
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
private val walletFeatureToggles: WalletFeatureToggles,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) : BasicAccountListSubscriber() {
override val isAddAndManageTokensEnabled: Boolean
@ -30,6 +34,7 @@ internal class SingleWalletSubscriber @AssistedInject constructor(
flow2 = getAppCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
flow5 = flowOf(tangemPayFeatureToggles.isMultipleCardsEnabled),
transform = ::updateState2,
)

View file

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