Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-01 18:58:08 +02:00
parent 4182005428
commit d6333458fa
15 changed files with 101 additions and 62 deletions

View file

@ -122,5 +122,9 @@
{
"name": "AND_15258_QUICK_TOP_UP_ENABLED",
"version": "undefined"
},
{
"name": "AND_15368_VISA_PAY_REDESIGN",
"version": "undefined"
}
]

View file

@ -464,6 +464,10 @@ val LocalRedesignEnabled = staticCompositionLocalOf<Boolean> {
false
}
val LocalVisaRedesignEnabled = staticCompositionLocalOf<Boolean> {
false
}
val LocalPowerSavingState = compositionLocalOf<PowerSavingState> {
error("No PowerSavingState provided")
}

View file

@ -1,3 +1,5 @@
package com.tangem.features.tangempay
interface TangemPayFeatureToggles
interface TangemPayFeatureToggles {
val isRedesignEnabled: Boolean
}

View file

@ -1,3 +1,11 @@
package com.tangem.features.tangempay
internal class DefaultTangemPayFeatureToggles : TangemPayFeatureToggles
import com.tangem.core.configtoggle.FeatureToggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
internal class DefaultTangemPayFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : TangemPayFeatureToggles {
override val isRedesignEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15368_VISA_PAY_REDESIGN)
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -15,6 +16,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.features.tangempay.components.cardDetails.DefaultTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.entity.TangemPayCardNavigation
@ -54,14 +56,16 @@ internal class TangemPayCardPageScreenComponent(
val cardDetailsState by cardDetailsBlockComponent.state.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
NavigationBar3ButtonsScrim()
TangemPayCardPageScreen(
state = state,
cardDetailsBlockComponent = cardDetailsBlockComponent,
cardDetailsState = cardDetailsState,
modifier = modifier,
)
bottomSheet.child?.instance?.BottomSheet()
CompositionLocalProvider(LocalVisaRedesignEnabled provides model.isRedesignEnabled()) {
NavigationBar3ButtonsScrim()
TangemPayCardPageScreen(
state = state,
cardDetailsBlockComponent = cardDetailsBlockComponent,
cardDetailsState = cardDetailsState,
modifier = modifier,
)
bottomSheet.child?.instance?.BottomSheet()
}
}
private fun bottomSheetChild(

View file

@ -1,6 +1,7 @@
package com.tangem.features.tangempay.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -16,7 +17,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.features.tangempay.components.txHistory.DefaultTangemPayTxHistoryComponent
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDetailsComponent
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
@ -72,24 +73,25 @@ internal class TangemPayDetailsComponent(
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val bottomSheet by bottomSheetSlot.subscribeAsState()
NavigationBar3ButtonsScrim()
if (LocalRedesignEnabled.current) {
TangemPayDetailsScreenV2(
state = state,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
modifier = modifier,
)
} else {
TangemPayDetailsScreen(
state = state,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
modifier = modifier,
)
CompositionLocalProvider(LocalVisaRedesignEnabled provides model.isRedesignEnabled()) {
NavigationBar3ButtonsScrim()
if (LocalVisaRedesignEnabled.current) {
TangemPayDetailsScreenV2(
state = state,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
modifier = modifier,
)
} else {
TangemPayDetailsScreen(
state = state,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
modifier = modifier,
)
}
bottomSheet.child?.instance?.BottomSheet()
}
bottomSheet.child?.instance?.BottomSheet()
}
private fun bottomSheetChild(

View file

@ -1,9 +1,11 @@
package com.tangem.features.tangempay.components.cardDetails
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
import com.tangem.features.tangempay.ui.TangemPayCard
@ -19,6 +21,8 @@ internal class DefaultTangemPayCardDetailsBlockComponent(
@Composable
override fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier) {
TangemPayCard(state, modifier)
CompositionLocalProvider(LocalVisaRedesignEnabled provides model.isRedesignEnabled()) {
TangemPayCard(state, modifier)
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.tangempay.di
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.tangempay.DefaultTangemPayFeatureToggles
import com.tangem.features.tangempay.TangemPayFeatureToggles
import dagger.Module
@ -14,7 +15,7 @@ internal object TangemPayDetailsModule {
@Provides
@Singleton
fun provideTangemPayFeatureToggles(): TangemPayFeatureToggles {
return DefaultTangemPayFeatureToggles()
fun provideTangemPayFeatureToggles(featureTogglesManager: FeatureTogglesManager): TangemPayFeatureToggles {
return DefaultTangemPayFeatureToggles(featureTogglesManager)
}
}

View file

@ -17,6 +17,7 @@ import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayCardDetailsBlockStateFactory
@ -57,6 +58,7 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
private val analytics: AnalyticsEventHandler,
private val router: Router,
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
private val payFeatureToggles: TangemPayFeatureToggles,
) : Model() {
private val params: TangemPayCardDetailsBlockComponent.Params = paramsContainer.require()
@ -90,6 +92,8 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
}
}
fun isRedesignEnabled(): Boolean = payFeatureToggles.isRedesignEnabled
private fun subscribeToCardChanges() {
paymentAccountStatusSupplier.invoke(params.userWalletId)
.onEach { state ->

View file

@ -12,7 +12,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
@ -35,6 +34,7 @@ import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.usecase.ChangeCardFrozenStateUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.AddFundsListener
import com.tangem.features.tangempay.components.ReissueCardListener
import com.tangem.features.tangempay.components.TangemPayCardPageComponent
@ -68,8 +68,8 @@ internal class TangemPayCardPageModel @Inject constructor(
private val cardDetailsRepository: TangemPayCardDetailsRepository,
private val uiMessageSender: UiMessageSender,
private val changeCardFrozenStateUseCase: ChangeCardFrozenStateUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val cardDetailsEventListener: CardDetailsEventListener,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener {
private val params: TangemPayCardPageComponent.Params = paramsContainer.require()
@ -136,8 +136,10 @@ internal class TangemPayCardPageModel @Inject constructor(
.launchIn(modelScope)
}
fun isRedesignEnabled(): Boolean = tangemPayFeatureToggles.isRedesignEnabled
private fun buildSettings(card: TangemPayCard): ImmutableList<TangemPayCardPageSetting> {
if (designFeatureToggles.isRedesignEnabled) return persistentListOf()
if (isRedesignEnabled()) return persistentListOf()
return persistentListOf(
TangemPayCardPageSetting(
title = TextReference.Res(R.string.tangempay_card_details_change_pin),
@ -163,7 +165,7 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private suspend fun subscribeOnDetailsState() {
if (!designFeatureToggles.isRedesignEnabled) return
if (!isRedesignEnabled()) return
cardDetailsEventListener.event.collect { event ->
val isDetailsShown = event == CardDetailsEvent.Show
uiState.update { state ->
@ -183,7 +185,7 @@ internal class TangemPayCardPageModel @Inject constructor(
}
private fun buildSettingsV2(card: TangemPayCard): ImmutableList<TangemPayCardPageSettingV2> {
if (!designFeatureToggles.isRedesignEnabled) return persistentListOf()
if (!isRedesignEnabled()) return persistentListOf()
return persistentListOf(
TangemPayCardPageSettingV2(
id = TangemPayCardPageSettingV2.Id.Details,

View file

@ -23,16 +23,17 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayTopUpData
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
import com.tangem.features.tangempay.TangemPayConstants
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.AddFundsListener
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
@ -74,6 +75,7 @@ internal class TangemPayDetailsModel @Inject constructor(
private val tangemPayWithdrawRepository: TangemPayWithdrawRepository,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener {
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
@ -151,6 +153,8 @@ internal class TangemPayDetailsModel @Inject constructor(
}
}
fun isRedesignEnabled(): Boolean = tangemPayFeatureToggles.isRedesignEnabled
private fun subscribeToCardFrozenState(cardId: String) {
cardDetailsRepository
.cardFrozenState(cardId)

View file

@ -23,7 +23,7 @@ import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.tangempay.details.impl.R
@ -37,7 +37,7 @@ private const val GRADIENT_RADIUS = 200F
@Composable
internal fun TangemPayAddToWalletBlock(state: AddToWalletBlockState, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
TangemPayAddToWalletBlockV2(state = state, modifier = modifier)
} else {
TangemPayAddToWalletBlockV1(state = state, modifier = modifier)

View file

@ -195,7 +195,7 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif
bottom.linkTo(parent.bottom)
}
.testTag(TangemPayTestTags.CARD_DETAILS_SHOW_BUTTON),
visible = !LocalRedesignEnabled.current || state.isLoading,
visible = !LocalVisaRedesignEnabled.current || state.isLoading,
) {
TangemPayCardDetailsCustomButton(
text = stringResourceSafe(id = R.string.tangempay_card_details_show_details),
@ -215,7 +215,7 @@ private fun CardTopBlock(modifier: Modifier = Modifier) {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
Text(
text = stringResourceSafe(R.string.tangempay_digital_card),
style = TangemTheme.typography3.body.medium,
@ -248,7 +248,7 @@ private fun ConstraintLayoutScope.CardNumberBlock(
cardNumberRef: ConstrainedLayoutReference,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
Text(
text = numberShort,
style = TangemTheme.typography3.body.medium,
@ -285,7 +285,7 @@ private fun CardDisplayName(state: DisplayNameState, modifier: Modifier = Modifi
@Composable
private fun DisplayOnlyCardDisplayName(state: DisplayNameState.Display, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
Row(
modifier = modifier.conditional(
condition = state.isEditingEnabled,
@ -337,7 +337,7 @@ private fun DisplayOnlyCardDisplayName(state: DisplayNameState.Display, modifier
@Composable
private fun EditingCardDisplayName(state: DisplayNameState.Editing, modifier: Modifier = Modifier) {
val isRedesignEnabled = LocalRedesignEnabled.current
val isRedesignEnabled = LocalVisaRedesignEnabled.current
val focusRequester = remember { FocusRequester() }
val placeholder = stringResourceSafe(R.string.tangempay_card_edit_name_placeholder)
@ -448,7 +448,7 @@ private fun TangemPayCardDetailsShownBlock(
Spacer(modifier = Modifier.weight(1f))
Row {
SpacerWMax()
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
// Must use dark theme locally for button cause card is dark
CompositionLocalProvider(LocalIsInDarkTheme provides true) {
TangemThemeRedesign {

View file

@ -29,10 +29,7 @@ import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.*
import com.tangem.domain.models.pay.TangemPayCardFrozenState
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
@ -51,7 +48,7 @@ internal fun TangemPayCardPageScreen(
cardDetailsState: TangemPayCardDetailsUM,
modifier: Modifier = Modifier,
) {
val isRedesignEnabled = LocalRedesignEnabled.current
val isRedesignEnabled = LocalVisaRedesignEnabled.current
Scaffold(
modifier = modifier,
topBar = {
@ -124,7 +121,7 @@ private fun TangemPayCardPageSettingsBlock(
settings: ImmutableList<TangemPayCardPageSetting>,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) return
if (LocalVisaRedesignEnabled.current) return
Column(
modifier = modifier
.fillMaxWidth()
@ -180,7 +177,7 @@ private fun CardPageTopBar(
items: ImmutableList<TangemPayDropDownItemUM>,
modifier: Modifier = Modifier,
) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
var isDropdownMenuShown by rememberSaveable { mutableStateOf(false) }
TangemTopBar(
modifier = modifier.statusBarsPadding(),
@ -210,7 +207,7 @@ private fun CardPageTopBar(
)
} else {
AppBarWithBackButton(
modifier = modifier,
modifier = modifier.statusBarsPadding(),
onBackClick = onBackClick,
)
}
@ -282,7 +279,10 @@ private fun TangemPayCardPageScreenPreviewV1() {
@Composable
private fun TangemPayCardPageScreenPreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
CompositionLocalProvider(
LocalRedesignEnabled provides true,
LocalVisaRedesignEnabled provides true,
) {
TangemPayCardPageScreen(
state = TangemPayCardPageUM.stub(),
cardDetailsBlockComponent = PreviewTangemPayCardDetailsBlockComponent(

View file

@ -31,16 +31,13 @@ import com.tangem.core.ui.ds2.shimmers.TextShimmer
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.res.*
import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayDailyLimitBlockState
@Composable
internal fun TangemPayDailyLimitBlock(state: TangemPayDailyLimitBlockState, modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) {
if (LocalVisaRedesignEnabled.current) {
CurrentLimitBlockV2(state, modifier)
} else {
TangemPayDailyLimitBlockV1(state, modifier)
@ -268,7 +265,7 @@ private fun SubtitleLimit(state: TangemPayDailyLimitBlockState, modifier: Modifi
@Composable
internal fun TangemPayDailyLimitErrorBlock(modifier: Modifier = Modifier) {
if (LocalRedesignEnabled.current) return
if (LocalVisaRedesignEnabled.current) return
Notification(
config = NotificationConfig(
title = resourceReference(R.string.tangempay_card_page_daily_limit_error_title),
@ -301,7 +298,10 @@ private fun Preview() {
@Composable
private fun PreviewV2() {
TangemThemePreviewRedesign {
CompositionLocalProvider(LocalRedesignEnabled provides true) {
CompositionLocalProvider(
LocalRedesignEnabled provides true,
LocalVisaRedesignEnabled provides true,
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {