Updated on 2026-08-14
This commit is contained in:
parent
50332186fa
commit
5f6767abaf
58 changed files with 85 additions and 717 deletions
|
|
@ -198,20 +198,9 @@ abstract class BaseTestCase : TestCase(
|
|||
// Version-gated toggles released in versions <= 6.0 — forced on so tests run against the actual
|
||||
// build even when the app version resolves to 1.0.0-SNAPSHOT on CI (then 1.0.0 < x.xx would
|
||||
// disable them). On the releases/6.0 branch every toggle with version <= 6.0 ships enabled.
|
||||
// 5.40
|
||||
"TWI_1377_MANAGE_FUNDS" to true,
|
||||
// 6.0
|
||||
"APP_REDESIGN_ENABLED" to true,
|
||||
"TWI_1326_YIELD_MODE_SWAP_ENABLED" to true,
|
||||
"AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED" to true,
|
||||
"AND_15120_SWAP_INTEGRATED_APPROVE" to true,
|
||||
"AND_15596_ONBOARDING_PUSH_NOTIFICATION_DOUBLE_ASK_AB_ENABLED" to true,
|
||||
"AND_15258_QUICK_TOP_UP_ENABLED" to true,
|
||||
"AND_15368_VISA_PAY_REDESIGN" to true,
|
||||
"AND_15364_VISA_PAY_CARD_CLOSE" to true,
|
||||
"AND_15489_EXPRESS_SHARE_BUTTON_ENABLED" to true,
|
||||
"AND_15235_VISA_MULTIPLE_CARDS" to true,
|
||||
"AND_15715_SWAP_BEST_DEX_RATE_ENABLED" to true,
|
||||
// 6.1
|
||||
"TWI_1638_VA_MVP0_ENABLED" to true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,26 +39,10 @@
|
|||
"name": "ADDRESS_SYNC_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15120_SWAP_INTEGRATED_APPROVE",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "TWI_1403_PUSH_NOTIFICATION_SETTINGS_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15596_ONBOARDING_PUSH_NOTIFICATION_DOUBLE_ASK_AB_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "TWI_1377_MANAGE_FUNDS",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15438_BACKEND_AUTHENTICATION_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -67,18 +51,6 @@
|
|||
"name": "AND_15482_SURVEYSPARROW_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15258_QUICK_TOP_UP_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15368_VISA_PAY_REDESIGN",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15364_VISA_PAY_CARD_CLOSE",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15741_VISA_PAY_REMOVE_ACCOUNT",
|
||||
"version": "undefined"
|
||||
|
|
@ -95,18 +67,6 @@
|
|||
"name": "AND_15632_GASLESS_YIELD_WITHDRAW_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15489_EXPRESS_SHARE_BUTTON_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15235_VISA_MULTIPLE_CARDS",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15715_SWAP_BEST_DEX_RATE_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "AND_15767_NEW_TX_HISTORY_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -115,10 +75,6 @@
|
|||
"name": "AND_14829_WARNINGS_REFACTORING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED",
|
||||
"version": "6.0"
|
||||
},
|
||||
{
|
||||
"name": "TWI_1522_MARKETING_BANNERS_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ internal class DefaultStakingFeatureToggles(
|
|||
return featureTogglesManager.isFeatureEnabled(toggle)
|
||||
}
|
||||
|
||||
override fun isSolanaUnstakeValidationEnabled(): Boolean {
|
||||
return featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
override fun isRegionUnavailableHandlingEnabled(): Boolean {
|
||||
return featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.AND_15231_STAKING_REGION_UNAVAILABLE_ENABLED,
|
||||
|
|
|
|||
|
|
@ -48,32 +48,6 @@ internal class DefaultStakingFeatureTogglesTest {
|
|||
verify(exactly = 0) { featureTogglesManager.isFeatureEnabled(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSolanaUnstakeValidationEnabled returns true when toggle enabled`() {
|
||||
every {
|
||||
featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED)
|
||||
} returns true
|
||||
|
||||
assertThat(toggles.isSolanaUnstakeValidationEnabled()).isTrue()
|
||||
|
||||
verify(exactly = 1) {
|
||||
featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isSolanaUnstakeValidationEnabled returns false when toggle disabled`() {
|
||||
every {
|
||||
featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED)
|
||||
} returns false
|
||||
|
||||
assertThat(toggles.isSolanaUnstakeValidationEnabled()).isFalse()
|
||||
|
||||
verify(exactly = 1) {
|
||||
featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16148_SOLANA_UNSTAKE_VALIDATION_ENABLED)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled WHEN isRegionUnavailableHandlingEnabled THEN returns true`() {
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -6,7 +6,5 @@ interface StakingFeatureToggles {
|
|||
|
||||
fun isIntegrationEnabled(integrationId: StakingIntegrationID): Boolean
|
||||
|
||||
fun isSolanaUnstakeValidationEnabled(): Boolean
|
||||
|
||||
fun isRegionUnavailableHandlingEnabled(): Boolean
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.features.pushnotifications
|
||||
|
||||
interface PushNotificationsFeatureToggles {
|
||||
|
||||
/** Kill switch for the onboarding "Double Ask" A/B experiment (`twi_1403_onboarding_push_notification_double_ask`). */
|
||||
val isOnboardingPushDoubleAskAbEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.features.pushnotifications.impl
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.pushnotifications.PushNotificationsFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultPushNotificationsFeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : PushNotificationsFeatureToggles {
|
||||
|
||||
override val isOnboardingPushDoubleAskAbEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.AND_15596_ONBOARDING_PUSH_NOTIFICATION_DOUBLE_ASK_AB_ENABLED,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package com.tangem.features.pushnotifications.impl.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.pushnotifications.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsBottomSheetComponent
|
||||
import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.impl.DefaultPushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.impl.model.PushNotificationsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -14,7 +12,6 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
|
@ -32,8 +29,4 @@ internal interface PushNotificationsModule {
|
|||
@IntoMap
|
||||
@ClassKey(PushNotificationsModel::class)
|
||||
fun bindModel(model: PushNotificationsModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindFeatureToggles(impl: DefaultPushNotificationsFeatureToggles): PushNotificationsFeatureToggles
|
||||
}
|
||||
|
|
@ -1,18 +1,13 @@
|
|||
package com.tangem.features.pushnotifications.impl.domain
|
||||
|
||||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.features.pushnotifications.PushNotificationsFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetPushNotificationsDoubleAskVariantUseCase @Inject constructor(
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
|
||||
private val abTestsManager: ABTestsManager,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): DoubleAskVariant {
|
||||
if (!pushNotificationsFeatureToggles.isOnboardingPushDoubleAskAbEnabled) {
|
||||
return DoubleAskVariant.Off
|
||||
}
|
||||
val variant = abTestsManager.getValue(AMPLITUDE_ID, DoubleAskVariant.Off.key)
|
||||
return DoubleAskVariant.fromKey(variant)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,22 @@ package com.tangem.features.pushnotifications.impl.domain
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.features.pushnotifications.PushNotificationsFeatureToggles
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class GetPushNotificationsDoubleAskVariantUseCaseTest {
|
||||
|
||||
private val featureToggles: PushNotificationsFeatureToggles = mockk()
|
||||
private val abTestsManager: ABTestsManager = mockk()
|
||||
|
||||
private val useCase = GetPushNotificationsDoubleAskVariantUseCase(
|
||||
pushNotificationsFeatureToggles = featureToggles,
|
||||
abTestsManager = abTestsManager,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle disabled WHEN invoke THEN returns Off and AB not queried`() = runTest {
|
||||
every { featureToggles.isOnboardingPushDoubleAskAbEnabled } returns false
|
||||
|
||||
val result = useCase()
|
||||
|
||||
assertThat(result).isEqualTo(DoubleAskVariant.Off)
|
||||
coVerify(exactly = 0) { abTestsManager.getValue(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled AND AB returns treatment WHEN invoke THEN returns On`() = runTest {
|
||||
every { featureToggles.isOnboardingPushDoubleAskAbEnabled } returns true
|
||||
fun `GIVEN AB returns treatment WHEN invoke THEN returns On`() = runTest {
|
||||
coEvery { abTestsManager.getValue(KEY, "control") } returns "treatment"
|
||||
|
||||
val result = useCase()
|
||||
|
|
@ -42,16 +27,14 @@ internal class GetPushNotificationsDoubleAskVariantUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled AND AB returns control WHEN invoke THEN returns Off`() = runTest {
|
||||
every { featureToggles.isOnboardingPushDoubleAskAbEnabled } returns true
|
||||
fun `GIVEN AB returns control WHEN invoke THEN returns Off`() = runTest {
|
||||
coEvery { abTestsManager.getValue(KEY, "control") } returns "control"
|
||||
|
||||
assertThat(useCase()).isEqualTo(DoubleAskVariant.Off)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled AND AB returns unknown WHEN invoke THEN returns Off`() = runTest {
|
||||
every { featureToggles.isOnboardingPushDoubleAskAbEnabled } returns true
|
||||
fun `GIVEN AB returns unknown WHEN invoke THEN returns Off`() = runTest {
|
||||
coEvery { abTestsManager.getValue(KEY, "control") } returns "unexpected_value"
|
||||
|
||||
assertThat(useCase()).isEqualTo(DoubleAskVariant.Off)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ import com.tangem.domain.staking.model.stakekit.action.StakingAction
|
|||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
||||
|
|
@ -156,7 +155,6 @@ internal class StakingModel @Inject constructor(
|
|||
private val coroutineScope: AppCoroutineScope,
|
||||
private val innerRouter: InnerStakingRouter,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val appRouter: AppRouter,
|
||||
) : Model(), StakingClickIntents {
|
||||
|
||||
|
|
@ -399,8 +397,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles
|
||||
.isSolanaUnstakeValidationEnabled(),
|
||||
)
|
||||
|
||||
addAll(
|
||||
|
|
@ -663,7 +659,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
value = value,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
checkSumLimitExceeded()
|
||||
|
|
@ -705,7 +700,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
checkSumLimitExceeded()
|
||||
|
|
@ -1388,7 +1382,6 @@ internal class StakingModel @Inject constructor(
|
|||
value = amountValue,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ internal class AmountChangeStateTransformer(
|
|||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||
private val value: String,
|
||||
private val integration: StakingIntegration,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||
|
|
@ -44,7 +43,6 @@ internal class AmountChangeStateTransformer(
|
|||
maxAmount = maxEnterAmount,
|
||||
integration = integration,
|
||||
actionType = prevState.actionType,
|
||||
isSolanaUnstakeValidationEnabled = isSolanaUnstakeValidationEnabled,
|
||||
).transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ internal class AmountMaxValueStateTransformer(
|
|||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||
private val actionType: StakingActionCommonType,
|
||||
private val integration: StakingIntegration,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||
|
|
@ -41,7 +40,6 @@ internal class AmountMaxValueStateTransformer(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
integration = integration,
|
||||
actionType = prevState.actionType,
|
||||
isSolanaUnstakeValidationEnabled = isSolanaUnstakeValidationEnabled,
|
||||
).transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ internal class AmountRequirementStateTransformer(
|
|||
private val maxAmount: EnterAmountBoundary,
|
||||
private val integration: StakingIntegration,
|
||||
private val actionType: StakingActionCommonType,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean = false,
|
||||
) : Transformer<AmountState> {
|
||||
override fun transform(prevState: AmountState): AmountState {
|
||||
return if (prevState is AmountState.Data) {
|
||||
|
|
@ -105,9 +104,7 @@ internal class AmountRequirementStateTransformer(
|
|||
)
|
||||
}
|
||||
is StakingActionCommonType.Exit -> {
|
||||
if (isSolanaUnstakeValidationEnabled &&
|
||||
isSolana(cryptoCurrencyStatus.currency.network.rawId)
|
||||
) {
|
||||
if (isSolana(cryptoCurrencyStatus.currency.network.rawId)) {
|
||||
getSolanaUnstakeError(amount = amountDecimal, staked = maxAmount.amount)
|
||||
} else {
|
||||
integration.exitArgs?.amountRequirement?.getError(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.domain.staking.*
|
|||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -114,9 +113,6 @@ internal abstract class StakingModelTestBase {
|
|||
private val coroutineScope: AppCoroutineScope = mockk()
|
||||
protected val innerRouter: InnerStakingRouter = mockk()
|
||||
protected val messageSender: UiMessageSender = mockk()
|
||||
protected val stakingFeatureToggles: StakingFeatureToggles = mockk {
|
||||
every { isSolanaUnstakeValidationEnabled() } returns false
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
@ -208,7 +204,6 @@ internal abstract class StakingModelTestBase {
|
|||
coroutineScope = coroutineScope,
|
||||
innerRouter = innerRouter,
|
||||
messageSender = messageSender,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,13 +85,11 @@ internal class AmountRequirementStateTransformerTest {
|
|||
staked: BigDecimal,
|
||||
exitMin: BigDecimal?,
|
||||
enterMin: BigDecimal? = null,
|
||||
enabled: Boolean = true,
|
||||
) = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus = solanaCryptoStatus(),
|
||||
maxAmount = EnterAmountBoundary(amount = staked, fiatAmount = null, fiatRate = null),
|
||||
integration = solanaExitIntegration(exitMin = exitMin, enterMin = enterMin),
|
||||
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
|
||||
isSolanaUnstakeValidationEnabled = enabled,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
@ -285,26 +283,12 @@ internal class AmountRequirementStateTransformerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN Solana validation disabled THEN partial unstake below minimum allowed`() {
|
||||
val transformer = solanaTransformer(
|
||||
staked = BigDecimal("5"),
|
||||
exitMin = BigDecimal("1"),
|
||||
enabled = false,
|
||||
)
|
||||
|
||||
val result = transformer.transform(amountState(BigDecimal("0.5"))) as AmountState.Data
|
||||
|
||||
assertThat(result.amountTextField.isError).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN validation enabled but currency is not Solana THEN Solana rule does not apply`() {
|
||||
fun `WHEN currency is not Solana THEN Solana rule does not apply`() {
|
||||
val transformer = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus, // relaxed mock: network.rawId is not "solana"
|
||||
maxAmount = EnterAmountBoundary(amount = BigDecimal("5"), fiatAmount = null, fiatRate = null),
|
||||
integration = exitIntegrationWith(minimum = null, maximum = null),
|
||||
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
|
||||
isSolanaUnstakeValidationEnabled = true,
|
||||
)
|
||||
|
||||
val result = transformer.transform(amountState(BigDecimal("0.5"))) as AmountState.Data
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ package com.tangem.features.swap
|
|||
|
||||
interface SwapFeatureToggles {
|
||||
val isYieldSwapEnabled: Boolean
|
||||
val isSwapSwitchToTransferEnabled: Boolean
|
||||
val isSwapIntegratedApproveEnabled: Boolean
|
||||
val isExpressShareButtonEnabled: Boolean
|
||||
val isSwapBestDexRateEnabled: Boolean
|
||||
val isHighFeeWarningEnabled: Boolean
|
||||
val isTronDexSwapEnabled: Boolean
|
||||
}
|
||||
|
|
@ -157,9 +157,7 @@ interface SwapInteractor {
|
|||
* `LIMITED`), loads its [com.tangem.blockchain.common.transaction.TransactionFee] and returns
|
||||
* both as [IntegratedApprovalData].
|
||||
*
|
||||
* Used by the integrated approve+swap flow when
|
||||
* `SwapFeatureToggles.isSwapIntegratedApproveEnabled` is ON and the quote requires an
|
||||
* allowance bump.
|
||||
* Used by the integrated approve+swap flow when the quote requires an allowance bump.
|
||||
*
|
||||
* @param approvalAmount LIMITED-mode swap amount (the user-input amount). Used when
|
||||
* [approveType] is `LIMITED`; ignored for `UNLIMITED`.
|
||||
|
|
|
|||
|
|
@ -404,8 +404,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
val isBalanceWithoutFeeEnough = isBalanceEnough(fromSwapCurrencyStatus, amount, null)
|
||||
val isIntegratedApproveActive = swapFeatureToggles.isSwapIntegratedApproveEnabled &&
|
||||
!hasIntegratedApprovalFallenBack(fromSwapCurrencyStatus, spenderAddress)
|
||||
val isIntegratedApproveActive = !hasIntegratedApprovalFallenBack(fromSwapCurrencyStatus, spenderAddress)
|
||||
|
||||
val isAllowanceSatisfied = if (isIntegratedApproveActive) {
|
||||
allowanceInfo !is AllowanceInfo.ResetNeeded
|
||||
|
|
@ -1973,7 +1972,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val isYieldSwap = fromSwapCurrencyStatus.isYieldSwapActive &&
|
||||
fromSwapCurrencyStatus.currency is CryptoCurrency.Token
|
||||
val isIntegratedApprovalNeeded = !isYieldSwap &&
|
||||
swapFeatureToggles.isSwapIntegratedApproveEnabled &&
|
||||
allowanceInfo is AllowanceInfo.NotEnough &&
|
||||
!hasIntegratedApprovalFallenBack(fromSwapCurrencyStatus, spenderAddress)
|
||||
swapState.copy(
|
||||
|
|
@ -2120,7 +2118,6 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
).getOrNull() ?: return quotesLoadedState.copy(permissionState = PermissionDataState.Empty)
|
||||
|
||||
val isIntegratedApprovalNeeded = !isYieldSwap &&
|
||||
swapFeatureToggles.isSwapIntegratedApproveEnabled &&
|
||||
allowanceInfo is AllowanceInfo.NotEnough &&
|
||||
!hasIntegratedApprovalFallenBack(fromSwapCurrencyStatus, quoteModel.allowanceContract)
|
||||
return quotesLoadedState.copy(
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkFeeCoverage
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.first
|
||||
import java.math.BigDecimal
|
||||
|
|
@ -54,7 +53,6 @@ import javax.inject.Inject
|
|||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class SwapTransferInteractorImpl @Inject constructor(
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
|
|
@ -306,7 +304,6 @@ class SwapTransferInteractorImpl @Inject constructor(
|
|||
fromSwapCurrency: CryptoCurrency?,
|
||||
toSwapCurrency: CryptoCurrency?,
|
||||
): Boolean {
|
||||
if (swapFeatureToggles.isSwapSwitchToTransferEnabled.not()) return false
|
||||
val isSameCurrency = when {
|
||||
fromSwapCurrency is CryptoCurrency.Coin && toSwapCurrency is CryptoCurrency.Coin -> {
|
||||
fromSwapCurrency.network.rawId == toSwapCurrency.network.rawId
|
||||
|
|
|
|||
|
|
@ -1618,11 +1618,6 @@ internal class SwapInteractorImplFindBestQuoteTest : SwapInteractorImplTestBase(
|
|||
private val spender = "0xDexRouter"
|
||||
private val tokenContract = "0xRegularToken"
|
||||
|
||||
@BeforeEach
|
||||
fun enableIntegrated() {
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NotEnough allowance with integrated active proceeds to PermissionSettings`() = runTest {
|
||||
stubAllowanceForSpender(
|
||||
|
|
|
|||
|
|
@ -169,7 +169,6 @@ internal class SwapInteractorImplLoadDexSwapDataNoFeeTest : SwapInteractorImplTe
|
|||
|
||||
@Test
|
||||
fun `GIVEN NotEnough allowance AND integrated active THEN permissionState is PermissionSettings`() = runTest {
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
stubAllowance(AllowanceInfo.NotEnough(allowance = BigDecimal.ZERO, requiredAmount = BigDecimal.ONE))
|
||||
|
||||
val state = runFindBestQuoteForToken()
|
||||
|
|
@ -181,7 +180,6 @@ internal class SwapInteractorImplLoadDexSwapDataNoFeeTest : SwapInteractorImplTe
|
|||
|
||||
@Test
|
||||
fun `GIVEN Enough allowance THEN permissionState is Empty`() = runTest {
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
stubAllowance(AllowanceInfo.Enough(allowance = BigDecimal("100")))
|
||||
|
||||
val state = runFindBestQuoteForToken()
|
||||
|
|
@ -194,7 +192,6 @@ internal class SwapInteractorImplLoadDexSwapDataNoFeeTest : SwapInteractorImplTe
|
|||
runTest {
|
||||
// [REDACTED_TASK_KEY] / iOS parity: yield swaps must never use the integrated approve+swap path.
|
||||
// The yield-module proxy allowance is granted at enrollment, so no in-flow approval is shown.
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
every { swapFeatureToggles.isYieldSwapEnabled } returns true
|
||||
coEvery { yieldModuleAddressProvider.getOrFetch(any(), any()) } returns YIELD_PROXY
|
||||
coEvery { walletManagersFacade.isSwapSpenderAllowed(any(), any(), any()) } returns true
|
||||
|
|
@ -221,26 +218,6 @@ internal class SwapInteractorImplLoadDexSwapDataNoFeeTest : SwapInteractorImplTe
|
|||
assertThat(state.permissionState).isEqualTo(PermissionDataState.Empty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN NotEnough allowance AND integrated toggle OFF THEN does not reach loadDexSwapDataNoFee`() = runTest {
|
||||
// With the integrated toggle off, NotEnough is not allowance-satisfied (requires Enough),
|
||||
// so manageDex does NOT enter loadDexSwapDataNoFee — getExchangeData is never called.
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns false
|
||||
stubAllowance(AllowanceInfo.NotEnough(allowance = BigDecimal.ZERO, requiredAmount = BigDecimal.ONE))
|
||||
|
||||
runFindBestQuoteForTokenRaw()
|
||||
|
||||
coVerify(exactly = 0) {
|
||||
repository.getExchangeData(
|
||||
userWallet = any(), fromContractAddress = any(), fromNetwork = any(),
|
||||
toContractAddress = any(), fromAddress = any(), toNetwork = any(),
|
||||
fromAmount = any(), fromDecimals = any(), toDecimals = any(),
|
||||
providerId = any(), rateType = any(), toAddress = any(),
|
||||
expressOperationType = any(), refundAddress = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private fun stubAllowance(info: AllowanceInfo) {
|
||||
|
|
@ -261,11 +238,6 @@ internal class SwapInteractorImplLoadDexSwapDataNoFeeTest : SwapInteractorImplTe
|
|||
return result[dexProvider] as SwapState.QuotesLoadedState
|
||||
}
|
||||
|
||||
private suspend fun runFindBestQuoteForTokenRaw() {
|
||||
val dexProvider = stubDexQuoteAndExchangeData()
|
||||
invokeFindBestQuote(dexProvider)
|
||||
}
|
||||
|
||||
private fun stubDexQuoteAndExchangeData(): com.tangem.feature.swap.domain.models.domain.SwapProvider {
|
||||
val dexProvider = buildSwapProvider(ExchangeProviderType.DEX)
|
||||
val quoteModel = buildQuoteModel(allowanceContract = SPENDER)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import com.tangem.feature.swap.domain.fee.TransactionFeeResult
|
|||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
|
@ -53,7 +52,6 @@ import java.math.BigDecimal
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class SwapTransferInteractorImplTest {
|
||||
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk()
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase = mockk()
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase = mockk()
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase = mockk()
|
||||
|
|
@ -72,7 +70,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
private val validateTransactionUseCase: ValidateTransactionUseCase = mockk()
|
||||
|
||||
private val sut = SwapTransferInteractorImpl(
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
getBalanceHidingSettingsUseCase = getBalanceHidingSettingsUseCase,
|
||||
isAccountsModeEnabledUseCase = isAccountsModeEnabledUseCase,
|
||||
|
|
@ -1314,23 +1311,8 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
// region shouldTransferInsteadOfSwap
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature toggle disabled WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns false
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
toSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
)
|
||||
|
||||
assertThat(result).isFalse()
|
||||
verify { swapFeatureToggles.isSwapSwitchToTransferEnabled }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN both coins on the same network WHEN shouldTransferInsteadOfSwap THEN return true`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
toSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
|
|
@ -1341,8 +1323,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN coins on different networks WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
toSwapCurrency = buildCoin(networkRawId = POLYGON),
|
||||
|
|
@ -1353,8 +1333,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN tokens with same network and same contract WHEN shouldTransferInsteadOfSwap THEN return true`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
toSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
|
|
@ -1365,8 +1343,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN tokens with same network but different contract WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
toSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDC_CONTRACT),
|
||||
|
|
@ -1377,8 +1353,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN tokens with same contract but different network WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
toSwapCurrency = buildToken(networkRawId = POLYGON, contractAddress = USDT_CONTRACT),
|
||||
|
|
@ -1389,8 +1363,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN coin from and token to WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
toSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
|
|
@ -1401,8 +1373,6 @@ internal class SwapTransferInteractorImplTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN token from and coin to WHEN shouldTransferInsteadOfSwap THEN return false`() {
|
||||
every { swapFeatureToggles.isSwapSwitchToTransferEnabled } returns true
|
||||
|
||||
val result = sut.shouldTransferInsteadOfSwap(
|
||||
fromSwapCurrency = buildToken(networkRawId = ETHEREUM, contractAddress = USDT_CONTRACT),
|
||||
toSwapCurrency = buildCoin(networkRawId = ETHEREUM),
|
||||
|
|
|
|||
|
|
@ -14,26 +14,6 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
|
|||
toggle = FeatureToggles.TWI_1326_YIELD_MODE_SWAP_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapSwitchToTransferEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15207_SWAP_SWITCH_TO_TRANSFER_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapIntegratedApproveEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
|
||||
)
|
||||
|
||||
override val isExpressShareButtonEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15489_EXPRESS_SHARE_BUTTON_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapBestDexRateEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15715_SWAP_BEST_DEX_RATE_ENABLED,
|
||||
) && isSwapIntegratedApproveEnabled
|
||||
|
||||
override val isHighFeeWarningEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.TWI_1367_HIGH_FEE_WARNING_ENABLED,
|
||||
|
|
|
|||
|
|
@ -28,18 +28,11 @@ internal object SwapProviderResolver {
|
|||
/**
|
||||
* Picks the best provider among [states].
|
||||
*
|
||||
* When [isSwapBestDexRateEnabled] is on and at least one DEX/DEX_BRIDGE provider is present, the
|
||||
* best-rated DEX provider wins; otherwise the overall best-rated provider is returned (the best
|
||||
* CEX when no DEX is available). "Best rated" = lowest from/to fiat ratio (most output per unit
|
||||
* of input). Returns null when [states] is empty.
|
||||
*
|
||||
* @param isSwapBestDexRateEnabled whether the Best DEX Rate feature toggle is on.
|
||||
* When at least one DEX/DEX_BRIDGE provider is present, the best-rated DEX provider wins; otherwise
|
||||
* the overall best-rated provider is returned (the best CEX when no DEX is available). "Best rated" =
|
||||
* lowest from/to fiat ratio (most output per unit of input). Returns null when [states] is empty.
|
||||
*/
|
||||
fun findBest(
|
||||
states: Map<SwapProvider, SwapState.QuotesLoadedState>,
|
||||
isSwapBestDexRateEnabled: Boolean,
|
||||
): SwapProvider? {
|
||||
if (!isSwapBestDexRateEnabled) return findBestRated(states)
|
||||
fun findBest(states: Map<SwapProvider, SwapState.QuotesLoadedState>): SwapProvider? {
|
||||
val dexStates = states.filterKeys { it.type.isDex() }
|
||||
return if (dexStates.isNotEmpty()) {
|
||||
findBestRated(dexStates)
|
||||
|
|
@ -54,7 +47,7 @@ internal object SwapProviderResolver {
|
|||
* Priority: FCA restriction → permission required → recommended → best rate → none. A best-rate
|
||||
* badge is shown only when more than one provider is considered, FCA restrictions are not applied,
|
||||
* and this row's quote carries no price-impact warning. Which best-rate badge it is depends on the
|
||||
* provider mix (only relevant when [isSwapBestDexRateEnabled] is on):
|
||||
* provider mix:
|
||||
* - [AdditionalBadge.BestTrade] ("Best rate") — always on the overall best-rated provider,
|
||||
* regardless of its type.
|
||||
* - [AdditionalBadge.BestDexRate] ("Best DEX rate") — only when both CEX and DEX providers are
|
||||
|
|
@ -62,22 +55,17 @@ internal object SwapProviderResolver {
|
|||
* shown on the best-rated DEX. When a DEX already is the overall best, or the set is CEX-only /
|
||||
* DEX-only, no separate "Best DEX rate" badge is shown.
|
||||
*
|
||||
* When [isSwapBestDexRateEnabled] is off, only the overall best provider gets [AdditionalBadge.BestTrade]
|
||||
* (legacy behaviour) and [AdditionalBadge.BestDexRate] is never produced.
|
||||
*
|
||||
* @param states all loaded quotes — used to find the best providers and to count considered providers.
|
||||
* @param provider the provider this row represents.
|
||||
* @param needApplyFCARestrictions whether FCA restrictions apply to the current user.
|
||||
* @param state this provider's [SwapState]; price-impact and permission are read from it when it
|
||||
* is a [SwapState.QuotesLoadedState]. Null for error rows (which only resolve to FCA / recommended / none).
|
||||
* @param isSwapBestDexRateEnabled whether the Best DEX Rate feature toggle is on.
|
||||
*/
|
||||
fun resolveBadge(
|
||||
states: Map<SwapProvider, SwapState.QuotesLoadedState>,
|
||||
provider: SwapProvider,
|
||||
needApplyFCARestrictions: Boolean,
|
||||
state: SwapState? = null,
|
||||
isSwapBestDexRateEnabled: Boolean,
|
||||
): AdditionalBadge {
|
||||
val priceImpact = (state as? SwapState.QuotesLoadedState)?.priceImpact
|
||||
val permissionState = (state as? SwapState.QuotesLoadedState)?.permissionState
|
||||
|
|
@ -90,7 +78,7 @@ internal object SwapProviderResolver {
|
|||
needApplyFCARestrictions && provider.isFCARestricted() -> AdditionalBadge.FCAWarningList
|
||||
permissionState is PermissionDataState.PermissionRequired -> AdditionalBadge.PermissionRequired
|
||||
provider.isRecommended -> AdditionalBadge.Recommended
|
||||
isBestRateBadgeAllowed -> resolveBestRateBadge(states, provider, isSwapBestDexRateEnabled)
|
||||
isBestRateBadgeAllowed -> resolveBestRateBadge(states, provider)
|
||||
else -> AdditionalBadge.Empty
|
||||
}
|
||||
}
|
||||
|
|
@ -102,16 +90,10 @@ internal object SwapProviderResolver {
|
|||
private fun resolveBestRateBadge(
|
||||
states: Map<SwapProvider, SwapState.QuotesLoadedState>,
|
||||
provider: SwapProvider,
|
||||
isSwapBestDexRateEnabled: Boolean,
|
||||
): AdditionalBadge {
|
||||
val overallBest = findBestRated(states)
|
||||
val isOverallBest = provider.providerId == overallBest?.providerId
|
||||
|
||||
// Toggle off → legacy behaviour: only the overall best provider gets the "Best rate" badge.
|
||||
if (!isSwapBestDexRateEnabled) {
|
||||
return if (isOverallBest) AdditionalBadge.BestTrade else AdditionalBadge.Empty
|
||||
}
|
||||
|
||||
val dexStates = states.filterKeys { it.type.isDex() }
|
||||
val hasDex = dexStates.isNotEmpty()
|
||||
val hasCex = states.keys.any { !it.type.isDex() }
|
||||
|
|
|
|||
|
|
@ -281,7 +281,6 @@ internal class SwapModel @Inject constructor(
|
|||
get() {
|
||||
val permissionState = dataState.getCurrentLoadedSwapState()?.permissionState
|
||||
return permissionState == PermissionDataState.Empty ||
|
||||
swapFeatureToggles.isSwapIntegratedApproveEnabled &&
|
||||
permissionState is PermissionDataState.PermissionSettings
|
||||
}
|
||||
|
||||
|
|
@ -1175,7 +1174,6 @@ internal class SwapModel @Inject constructor(
|
|||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
states = loadedStates,
|
||||
state = state,
|
||||
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
||||
)
|
||||
val swapFee = getSelectedSwapFee()
|
||||
uiState = stateBuilder.createQuotesLoadedState(
|
||||
|
|
@ -1272,7 +1270,6 @@ internal class SwapModel @Inject constructor(
|
|||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
states = loadedStates,
|
||||
state = state,
|
||||
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
||||
)
|
||||
uiState = stateBuilder.createQuotesErrorState(
|
||||
uiStateHolder = uiState,
|
||||
|
|
@ -1327,10 +1324,7 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
return if (consideredProviders.isNotEmpty()) {
|
||||
val successLoadedData = consideredProviders.getLastLoadedSuccessStates()
|
||||
val bestQuotesProvider = SwapProviderResolver.findBest(
|
||||
states = successLoadedData,
|
||||
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
||||
)
|
||||
val bestQuotesProvider = SwapProviderResolver.findBest(states = successLoadedData)
|
||||
val currentSelected = dataState.selectedProvider
|
||||
if (currentSelected != null && consideredProviders.keys.contains(currentSelected)) {
|
||||
// logic for always choose best if already selected provider
|
||||
|
|
@ -2111,7 +2105,6 @@ internal class SwapModel @Inject constructor(
|
|||
selectedProviderId = providerId,
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
providersStates = dataState.lastLoadedSwapStates,
|
||||
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
) { uiState = stateBuilder.dismissBottomSheet(uiState) }
|
||||
},
|
||||
|
|
@ -2663,8 +2656,7 @@ internal class SwapModel @Inject constructor(
|
|||
val swapDataForCall = resolveDexSwapDataForFee(quoteState)
|
||||
.getOrElse { return Either.Left(it) }
|
||||
|
||||
val integratedSettings = (quoteState.permissionState as? PermissionDataState.PermissionSettings)
|
||||
?.takeIf { swapFeatureToggles.isSwapIntegratedApproveEnabled }
|
||||
val integratedSettings = quoteState.permissionState as? PermissionDataState.PermissionSettings
|
||||
|
||||
return swapInteractor.loadSwapFee(
|
||||
quotesLoadedState = quoteState,
|
||||
|
|
@ -2833,8 +2825,7 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun isPermissionNotificationShown(): Boolean {
|
||||
val permissionState = dataState.getCurrentLoadedSwapState()?.permissionState
|
||||
val isApprovalIntegrated = swapFeatureToggles.isSwapIntegratedApproveEnabled &&
|
||||
permissionState is PermissionDataState.PermissionSettings
|
||||
val isApprovalIntegrated = permissionState is PermissionDataState.PermissionSettings
|
||||
return permissionState != null && permissionState !is PermissionDataState.Empty && !isApprovalIntegrated
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1220,7 +1220,6 @@ internal class StateBuilder(
|
|||
pricesLowerBest: Map<String, Float>,
|
||||
providersStates: Map<SwapProvider, SwapState>,
|
||||
needApplyFCARestrictions: Boolean,
|
||||
isSwapBestDexRateEnabled: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
): SwapStateHolder {
|
||||
val successStates = providersStates.getLastLoadedSuccessStates()
|
||||
|
|
@ -1231,7 +1230,6 @@ internal class StateBuilder(
|
|||
provider = entry.key,
|
||||
needApplyFCARestrictions = needApplyFCARestrictions,
|
||||
state = entry.value,
|
||||
isSwapBestDexRateEnabled = isSwapBestDexRateEnabled,
|
||||
)
|
||||
entry.convertToProviderBottomSheetState(
|
||||
pricesLowerBest = pricesLowerBest,
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import java.math.BigDecimal
|
|||
* row-badge resolution ([SwapProviderResolver.resolveBadge]).
|
||||
*
|
||||
* Ranking metric: best provider == lowest `from/to` fiat ratio == highest `to` fiat output for the
|
||||
* same `from` input. "Best DEX Rate" prefers the best-rated DEX/DEX_BRIDGE provider when the feature
|
||||
* is on and any DEX is present.
|
||||
* same `from` input. "Best DEX Rate" prefers the best-rated DEX/DEX_BRIDGE provider when any DEX is
|
||||
* present.
|
||||
*/
|
||||
internal class SwapProviderResolverTest {
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ internal class SwapProviderResolverTest {
|
|||
dexBridge to quote(fromFiat = "100", toFiat = "105"),
|
||||
)
|
||||
|
||||
val best = SwapProviderResolver.findBest(states, isSwapBestDexRateEnabled = true)
|
||||
val best = SwapProviderResolver.findBest(states)
|
||||
|
||||
assertThat(best).isEqualTo(dex1)
|
||||
}
|
||||
|
|
@ -62,23 +62,11 @@ internal class SwapProviderResolverTest {
|
|||
cex2 to quote(fromFiat = "100", toFiat = "120"), // best CEX
|
||||
)
|
||||
|
||||
val best = SwapProviderResolver.findBest(states, isSwapBestDexRateEnabled = true)
|
||||
val best = SwapProviderResolver.findBest(states)
|
||||
|
||||
assertThat(best).isEqualTo(cex2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN best dex rate off WHEN findBest THEN best overall regardless of type`() {
|
||||
val states = mapOf(
|
||||
cex1 to quote(fromFiat = "100", toFiat = "120"), // best overall (a CEX)
|
||||
dex1 to quote(fromFiat = "100", toFiat = "110"),
|
||||
)
|
||||
|
||||
val best = SwapProviderResolver.findBest(states, isSwapBestDexRateEnabled = false)
|
||||
|
||||
assertThat(best).isEqualTo(cex1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN DEX_BRIDGE is the best DEX WHEN findBest with best dex rate on THEN DEX_BRIDGE selected`() {
|
||||
val states = mapOf(
|
||||
|
|
@ -87,7 +75,7 @@ internal class SwapProviderResolverTest {
|
|||
dexBridge to quote(fromFiat = "100", toFiat = "115"), // best among DEX-based
|
||||
)
|
||||
|
||||
val best = SwapProviderResolver.findBest(states, isSwapBestDexRateEnabled = true)
|
||||
val best = SwapProviderResolver.findBest(states)
|
||||
|
||||
assertThat(best).isEqualTo(dexBridge)
|
||||
}
|
||||
|
|
@ -110,7 +98,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = dex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
// Overall best is the DEX → it gets the single "Best rate" badge, NOT "Best DEX rate".
|
||||
|
|
@ -129,7 +116,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -150,7 +136,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.BestTrade)
|
||||
|
|
@ -169,7 +154,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = dex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
// CEX wins overall, so the best DEX additionally gets the "Best DEX rate" badge.
|
||||
|
|
@ -189,7 +173,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = dexBridge,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dexBridge),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -209,7 +192,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = dex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.BestTrade)
|
||||
|
|
@ -227,7 +209,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = dexBridge,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dexBridge),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -247,51 +228,11 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.BestTrade)
|
||||
}
|
||||
|
||||
// --- Toggle off → only the overall best gets BestTrade; "Best DEX rate" is never produced.
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle off AND both types present with CEX best WHEN resolveBadge for the CEX THEN BestTrade`() {
|
||||
val states = mapOf(
|
||||
cex1 to quote(fromFiat = "100", toFiat = "120"), // best overall
|
||||
dex1 to quote(fromFiat = "100", toFiat = "110"),
|
||||
)
|
||||
|
||||
val badge = SwapProviderResolver.resolveBadge(
|
||||
states = states,
|
||||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = false,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.BestTrade)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle off AND both types present with CEX best WHEN resolveBadge for the DEX THEN Empty`() {
|
||||
val states = mapOf(
|
||||
cex1 to quote(fromFiat = "100", toFiat = "120"), // best overall
|
||||
dex1 to quote(fromFiat = "100", toFiat = "110"),
|
||||
)
|
||||
|
||||
val badge = SwapProviderResolver.resolveBadge(
|
||||
states = states,
|
||||
provider = dex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(dex1),
|
||||
isSwapBestDexRateEnabled = false,
|
||||
)
|
||||
|
||||
// Toggle off → no "Best DEX rate" badge even though a DEX is present and not the overall best.
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN FCA restricted provider AND restrictions on WHEN resolveBadge THEN FCAWarningList`() {
|
||||
val restricted = provider(id = "changelly", type = ExchangeProviderType.CEX, isRecommended = true)
|
||||
|
|
@ -305,7 +246,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = restricted,
|
||||
needApplyFCARestrictions = true,
|
||||
state = states.getValue(restricted),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.FCAWarningList)
|
||||
|
|
@ -326,7 +266,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = restricted,
|
||||
needApplyFCARestrictions = true,
|
||||
state = states.getValue(restricted),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.FCAWarningList)
|
||||
|
|
@ -346,7 +285,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = restricted,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(restricted),
|
||||
isSwapBestDexRateEnabled = false,
|
||||
)
|
||||
|
||||
// Restrictions are off → the restricted id is ignored and the normal best-rate badge wins.
|
||||
|
|
@ -365,7 +303,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = true,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
// FCA restrictions globally on suppress the best-rate badge even for non-restricted providers.
|
||||
|
|
@ -388,7 +325,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.PermissionRequired)
|
||||
|
|
@ -407,7 +343,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = recommended,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(recommended),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Recommended)
|
||||
|
|
@ -422,7 +357,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -440,7 +374,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex2, // not the best
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex2),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -458,7 +391,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = states.getValue(cex1),
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
@ -476,7 +408,6 @@ internal class SwapProviderResolverTest {
|
|||
provider = cex1,
|
||||
needApplyFCARestrictions = false,
|
||||
state = null,
|
||||
isSwapBestDexRateEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(badge).isEqualTo(ProviderState.AdditionalBadge.Empty)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.feature.swap.model
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
import io.mockk.every
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ import org.junit.jupiter.api.Test
|
|||
*
|
||||
* The getter is `true` when the current loaded swap state needs no approval:
|
||||
* - always when [PermissionDataState.Empty]
|
||||
* - additionally for [PermissionDataState.PermissionSettings] when the integrated-approve toggle is ON
|
||||
* - additionally for [PermissionDataState.PermissionSettings]
|
||||
*/
|
||||
internal class SwapModelIsPermissionNotNeededTest : SwapModelTestBase() {
|
||||
|
||||
|
|
@ -20,11 +19,7 @@ internal class SwapModelIsPermissionNotNeededTest : SwapModelTestBase() {
|
|||
setUpBase()
|
||||
}
|
||||
|
||||
private fun modelWithPermissionState(
|
||||
permissionState: PermissionDataState,
|
||||
isIntegratedApproveEnabled: Boolean,
|
||||
): SwapModel {
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns isIntegratedApproveEnabled
|
||||
private fun modelWithPermissionState(permissionState: PermissionDataState): SwapModel {
|
||||
val provider = swapProvider()
|
||||
val model = createModel()
|
||||
model.dataState = model.dataState.copy(
|
||||
|
|
@ -35,48 +30,23 @@ internal class SwapModelIsPermissionNotNeededTest : SwapModelTestBase() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle OFF and Empty permission THEN permission is not needed`() {
|
||||
val model = modelWithPermissionState(PermissionDataState.Empty, isIntegratedApproveEnabled = false)
|
||||
fun `GIVEN Empty permission THEN permission is not needed`() {
|
||||
val model = modelWithPermissionState(PermissionDataState.Empty)
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle OFF and PermissionSettings THEN permission is needed`() {
|
||||
val model = modelWithPermissionState(permissionSettings(), isIntegratedApproveEnabled = false)
|
||||
fun `GIVEN PermissionSettings THEN permission is not needed`() {
|
||||
val model = modelWithPermissionState(permissionSettings())
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isFalse()
|
||||
assertThat(model.isPermissionNotNeeded).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle OFF and PermissionRequired THEN permission is needed`() {
|
||||
fun `GIVEN PermissionRequired THEN permission is needed`() {
|
||||
val model = modelWithPermissionState(
|
||||
PermissionDataState.PermissionRequired(isResetApproval = false, spenderAddress = "0x"),
|
||||
isIntegratedApproveEnabled = false,
|
||||
)
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle ON and Empty permission THEN permission is not needed`() {
|
||||
val model = modelWithPermissionState(PermissionDataState.Empty, isIntegratedApproveEnabled = true)
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle ON and PermissionSettings THEN permission is not needed`() {
|
||||
val model = modelWithPermissionState(permissionSettings(), isIntegratedApproveEnabled = true)
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle ON and PermissionRequired THEN permission is needed`() {
|
||||
val model = modelWithPermissionState(
|
||||
PermissionDataState.PermissionRequired(isResetApproval = false, spenderAddress = "0x"),
|
||||
isIntegratedApproveEnabled = true,
|
||||
)
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isFalse()
|
||||
|
|
@ -84,7 +54,6 @@ internal class SwapModelIsPermissionNotNeededTest : SwapModelTestBase() {
|
|||
|
||||
@Test
|
||||
fun `GIVEN no current loaded state THEN permission is needed`() {
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
val model = createModel()
|
||||
|
||||
assertThat(model.isPermissionNotNeeded).isFalse()
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ internal abstract class SwapModelTestBase {
|
|||
coEvery { shouldShowStoriesUseCase.invokeSync(any()) } returns false
|
||||
coEvery { initialCurrenciesResolver.invoke(any(), any(), any(), any()) } returns (null to null)
|
||||
every { getSelectedAppCurrencyUseCase.invoke() } returns emptyFlow()
|
||||
every { swapFeatureToggles.isSwapIntegratedApproveEnabled } returns true
|
||||
}
|
||||
|
||||
protected fun createParams(): SwapComponent.Params = SwapComponent.Params(
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.tangem.features.tangempay
|
|||
|
||||
interface TangemPayFeatureToggles {
|
||||
val isRedesignEnabled: Boolean
|
||||
val isCloseCardEnabled: Boolean
|
||||
val isRemoveAccountEnabled: Boolean
|
||||
val isMultipleCardsEnabled: Boolean
|
||||
val isTiersPlusPlanEnabled: Boolean
|
||||
val isCashbackEnabled: Boolean
|
||||
}
|
||||
|
|
@ -7,18 +7,11 @@ internal class DefaultTangemPayFeatureToggles(
|
|||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TangemPayFeatureToggles {
|
||||
override val isRedesignEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15368_VISA_PAY_REDESIGN) &&
|
||||
featureTogglesManager.isFeatureEnabled(FeatureToggles.APP_REDESIGN_ENABLED)
|
||||
|
||||
override val isCloseCardEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15364_VISA_PAY_CARD_CLOSE)
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.APP_REDESIGN_ENABLED)
|
||||
|
||||
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)
|
||||
|
||||
override val isTiersPlusPlanEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_16041_VISA_TIERS_PLUS_PLAN)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ internal class TangemPayDetailsStateFactory(
|
|||
private val intents: TangemPayDetailIntents,
|
||||
private val isRedesignEnabled: Boolean,
|
||||
private val isRemoveAccountEnabled: Boolean,
|
||||
private val isMultipleCardsEnabled: Boolean,
|
||||
private val isTiersPlusPlanEnabled: Boolean,
|
||||
) {
|
||||
private val notificationFactory = TangemPayDetailsNotificationFactory(
|
||||
|
|
@ -100,7 +99,6 @@ internal class TangemPayDetailsStateFactory(
|
|||
),
|
||||
cardsBlockState = TangemPayDetailsBalanceBlockState.CardsBlockState(
|
||||
cards = status.cards
|
||||
.let { if (isMultipleCardsEnabled) it else it.take(1) }
|
||||
.map { cardItem ->
|
||||
TangemPayDetailsBalanceBlockState.Card(
|
||||
lastDigits = cardItem.lastDigits,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
|
||||
private val cryptoCurrency
|
||||
get() = currentStatus.value.cryptoCurrency
|
||||
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isMultipleCardsEnabled
|
||||
|
||||
val uiState: StateFlow<TangemPayCardPageUM>
|
||||
field = MutableStateFlow(
|
||||
|
|
@ -185,7 +184,7 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
val status = state.value
|
||||
if (status !is PaymentAccountStatusValue.Loaded || status.source != StatusSource.ACTUAL) return
|
||||
|
||||
val cards = status.cards.let { if (isMultipleCardsEnabled) it else it.take(1) }
|
||||
val cards = status.cards
|
||||
val newIds = cards.mapTo(mutableSetOf()) { it.id }
|
||||
|
||||
cardControllers.keys.filterNot { it in newIds }.toList().forEach { removedId ->
|
||||
|
|
@ -334,7 +333,6 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
),
|
||||
),
|
||||
)
|
||||
if (tangemPayFeatureToggles.isCloseCardEnabled) {
|
||||
add(
|
||||
TangemPayDropDownItemUM(
|
||||
title = TextReference.Res(R.string.tangem_pay_close_card_popup_primary_button_title),
|
||||
|
|
@ -357,7 +355,6 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
isEnabled = !isLastCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,15 +111,12 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
val cryptoCurrency
|
||||
get() = currentStatus.value.cryptoCurrency
|
||||
|
||||
private val isMultipleCardsEnabled: Boolean get() = tangemPayFeatureToggles.isMultipleCardsEnabled
|
||||
|
||||
private val stateFactory = TangemPayDetailsStateFactory(
|
||||
onBack = router::pop,
|
||||
onOpenMenu = ::onOpenMenu,
|
||||
intents = this,
|
||||
isRedesignEnabled = isRedesignEnabled(),
|
||||
isRemoveAccountEnabled = tangemPayFeatureToggles.isRemoveAccountEnabled,
|
||||
isMultipleCardsEnabled = isMultipleCardsEnabled,
|
||||
isTiersPlusPlanEnabled = tangemPayFeatureToggles.isTiersPlusPlanEnabled,
|
||||
)
|
||||
|
||||
|
|
@ -476,17 +473,6 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
override fun onAddCardClick() {
|
||||
analytics.send(TangemPayAnalyticsEvents.AddExtraCardClicked())
|
||||
if (!isMultipleCardsEnabled) {
|
||||
analytics.send(TangemPayAnalyticsEvents.FakeDoorPopupDisplayed())
|
||||
uiMessageSender.send(
|
||||
TangemPayMessagesFactory.createFutureFeature(
|
||||
onGotItClick = {
|
||||
analytics.send(TangemPayAnalyticsEvents.FakeDoorGotitClicked())
|
||||
},
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
val offer = getCustomerOffers.additionalCardOffer(userWalletId).getOrNull()
|
||||
if (offer == null) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ internal class TangemPayDetailsStateFactoryTest {
|
|||
intents = intents,
|
||||
isRedesignEnabled = true,
|
||||
isRemoveAccountEnabled = true,
|
||||
isMultipleCardsEnabled = true,
|
||||
isTiersPlusPlanEnabled = true,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.features.tokendetails
|
||||
|
||||
interface TokenDetailsFeatureToggles {
|
||||
val isQuickTopUpEnabled: Boolean
|
||||
val isManageFundsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.feature.tokendetails
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultTokenDetailsFeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokenDetailsFeatureToggles {
|
||||
|
||||
override val isQuickTopUpEnabled: Boolean = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15258_QUICK_TOP_UP_ENABLED,
|
||||
)
|
||||
|
||||
override val isManageFundsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.TWI_1377_MANAGE_FUNDS)
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.tokendetails.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.feature.tokendetails.DefaultTokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TokenDetailsFeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTokenDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokenDetailsFeatureToggles {
|
||||
return DefaultTokenDetailsFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,15 +8,12 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.QuickTopUpBlockUM
|
||||
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class QuickTopUpBlockFactory @Inject constructor(
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
) {
|
||||
internal class QuickTopUpBlockFactory @Inject constructor() {
|
||||
|
||||
fun build(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -25,8 +22,6 @@ internal class QuickTopUpBlockFactory @Inject constructor(
|
|||
onPresetClick: (BigDecimal, String) -> Unit,
|
||||
onOtherClick: () -> Unit,
|
||||
): QuickTopUpBlockUM? {
|
||||
if (!featureToggles.isQuickTopUpEnabled) return null
|
||||
|
||||
val amount = currencyStatus.value.amount
|
||||
if (amount == null || !amount.isZero()) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.ExpressTr
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -47,7 +46,6 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
onrampStatusFactory: OnrampStatusFactory.Factory,
|
||||
exchangeStatusFactory: ExchangeStatusFactory.Factory,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val txHistoryFeatureToggles: TxHistoryFeatureToggles,
|
||||
) {
|
||||
|
||||
|
|
@ -221,7 +219,6 @@ internal class ExpressStatusFactory @AssistedInject constructor(
|
|||
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(
|
||||
config = this,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = swapFeatureToggles.isExpressShareButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,12 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.e
|
|||
@Composable
|
||||
internal fun ExpressStatusBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
isExpressShareButtonEnabled: Boolean,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
) { content: ExpressStatusBottomSheetConfig ->
|
||||
if (isExpressShareButtonEnabled) {
|
||||
Box {
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
|
||||
|
|
@ -38,7 +36,6 @@ internal fun ExpressStatusBottomSheet(
|
|||
is ExchangeUM -> ExchangeStatusBottomSheetContent(
|
||||
state = state,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = true,
|
||||
)
|
||||
}
|
||||
BottomFade(
|
||||
|
|
@ -47,19 +44,6 @@ internal fun ExpressStatusBottomSheet(
|
|||
)
|
||||
ExpressShareContent(state = content.value)
|
||||
}
|
||||
} else {
|
||||
when (val state = content.value) {
|
||||
is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(
|
||||
state = state,
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
is ExchangeUM -> ExchangeStatusBottomSheetContent(
|
||||
state = state,
|
||||
extraContent = extraContent,
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +60,6 @@ private fun PreviewExpressStatusBottomSheet(
|
|||
onDismissRequest = {},
|
||||
content = param,
|
||||
),
|
||||
isExpressShareButtonEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerH10
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.notifications.CurrencyNotification
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -38,11 +37,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.E
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun ExchangeStatusBottomSheetContent(
|
||||
state: ExchangeUM,
|
||||
isExpressShareButtonEnabled: Boolean,
|
||||
extraContent: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM, extraContent: (@Composable () -> Unit)? = null) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
|
|
@ -97,11 +92,7 @@ internal fun ExchangeStatusBottomSheetContent(
|
|||
isAutoDisposable = state.activeStatus?.isAutoDisposable == true,
|
||||
onClick = state.info.onDisposeExpressStatus,
|
||||
)
|
||||
if (isExpressShareButtonEnabled) {
|
||||
SpacerH(80.dp)
|
||||
} else {
|
||||
SpacerH24()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import com.tangem.domain.onramp.model.OnrampAvailability
|
|||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.error.OnrampError
|
||||
import com.tangem.features.tokendetails.TokenDetailsFeatureToggles
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -19,10 +18,7 @@ import java.math.BigDecimal
|
|||
|
||||
internal class QuickTopUpBlockFactoryTest {
|
||||
|
||||
private val featureToggles: TokenDetailsFeatureToggles = mockk {
|
||||
every { isQuickTopUpEnabled } returns true
|
||||
}
|
||||
private val factory = QuickTopUpBlockFactory(featureToggles)
|
||||
private val factory = QuickTopUpBlockFactory()
|
||||
|
||||
private val zeroBalanceStatus: CryptoCurrencyStatus = mockk {
|
||||
every { value } returns mockk<CryptoCurrencyStatus.Loaded> {
|
||||
|
|
@ -69,24 +65,6 @@ internal class QuickTopUpBlockFactoryTest {
|
|||
|
||||
private val notSupported: OnrampAvailability = OnrampAvailability.NotSupported(country = countryMock)
|
||||
|
||||
@Test
|
||||
fun `returns null when feature toggle is disabled`() {
|
||||
val disabledToggles: TokenDetailsFeatureToggles = mockk {
|
||||
every { isQuickTopUpEnabled } returns false
|
||||
}
|
||||
val disabledFactory = QuickTopUpBlockFactory(disabledToggles)
|
||||
|
||||
val result = disabledFactory.build(
|
||||
currencyStatus = zeroBalanceStatus,
|
||||
isHistoryEmpty = true,
|
||||
onrampAvailability = availableUsd.right(),
|
||||
onPresetClick = { _, _ -> },
|
||||
onOtherClick = {},
|
||||
)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when balance is non-zero`() {
|
||||
val result = factory.build(
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.features.wallet.featuretoggles
|
||||
|
||||
/**
|
||||
* Wallet feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface WalletFeatureToggles {
|
||||
|
||||
val isManageFundsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -67,7 +67,6 @@ import com.tangem.features.biometry.AskBiometryComponent
|
|||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.pushnotificationsettings.PushNotificationSettingsFeatureToggles
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
@ -125,7 +124,6 @@ internal class WalletModel @Inject constructor(
|
|||
private val resolveQrSendTargetsUseCase: ResolveQrSendTargetsUseCase,
|
||||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val pushNotificationSettingsFeatureToggles: PushNotificationSettingsFeatureToggles,
|
||||
private val addressBookFeatureToggles: AddressBookFeatureToggles,
|
||||
private val startAssetsDiscoveryUseCase: StartAssetsDiscoveryUseCase,
|
||||
|
|
@ -577,7 +575,6 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = walletFeatureToggles.isManageFundsEnabled,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -624,7 +621,6 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = walletFeatureToggles.isManageFundsEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -646,7 +642,6 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = walletFeatureToggles.isManageFundsEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -663,7 +658,6 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = walletFeatureToggles.isManageFundsEnabled,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -725,7 +719,6 @@ internal class WalletModel @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = walletFeatureToggles.isManageFundsEnabled,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.feature.wallet.di
|
||||
|
||||
import com.tangem.feature.wallet.featuretoggles.DefaultWalletFeatureToggles
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface WalletFeatureTogglesModule {
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindWalletFeatureToggles(toggles: DefaultWalletFeatureToggles): WalletFeatureToggles
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.feature.wallet.featuretoggles
|
||||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultWalletFeatureToggles @Inject constructor(
|
||||
private val featureToggles: FeatureTogglesManager,
|
||||
) : WalletFeatureToggles {
|
||||
|
||||
override val isManageFundsEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled(FeatureToggles.TWI_1377_MANAGE_FUNDS)
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ internal class AddWalletTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -21,7 +20,6 @@ internal class AddWalletTransformer(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = isManageFundsEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ internal class InitializeWalletsTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -36,7 +35,6 @@ internal class InitializeWalletsTransformer(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = isManageFundsEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -156,11 +154,7 @@ internal class InitializeWalletsTransformer(
|
|||
onClick = {},
|
||||
)
|
||||
|
||||
val lastButton = if (isManageFundsEnabled) {
|
||||
WalletManageButton.Transfer(enabled = false, dimContent = false, onClick = {})
|
||||
} else {
|
||||
WalletManageButton.Sell(enabled = false, dimContent = false, onClick = {})
|
||||
}
|
||||
val lastButton = WalletManageButton.Transfer(enabled = false, dimContent = false, onClick = {})
|
||||
|
||||
return persistentListOf(
|
||||
firstButton,
|
||||
|
|
@ -193,21 +187,12 @@ internal class InitializeWalletsTransformer(
|
|||
onClick = {},
|
||||
).buttonUM,
|
||||
)
|
||||
if (isManageFundsEnabled) {
|
||||
add(
|
||||
WalletActionButtons.Transfer(
|
||||
isEnabled = false,
|
||||
onClick = {},
|
||||
).buttonUM,
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
WalletActionButtons.Sell(
|
||||
isEnabled = false,
|
||||
onClick = {},
|
||||
).buttonUM,
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ internal class ReinitializeNewWalletTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -33,7 +32,6 @@ internal class ReinitializeNewWalletTransformer(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = isManageFundsEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ internal class ReinitializeWalletTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) : WalletStateTransformer(userWalletId = userWallet.walletId) {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -28,7 +27,6 @@ internal class ReinitializeWalletTransformer(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = isManageFundsEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ internal class UnlockWalletTransformer(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
private val walletLoadingStateFactory by lazy {
|
||||
|
|
@ -26,7 +25,6 @@ internal class UnlockWalletTransformer(
|
|||
clickIntents = clickIntents,
|
||||
walletImageResolver = walletImageResolver,
|
||||
getWalletIconUseCase = getWalletIconUseCase,
|
||||
isManageFundsEnabled = isManageFundsEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ internal class WalletLoadingStateFactory(
|
|||
private val clickIntents: WalletClickIntents,
|
||||
private val walletImageResolver: WalletImageResolver,
|
||||
private val getWalletIconUseCase: GetWalletIconUseCase,
|
||||
private val isManageFundsEnabled: Boolean,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet): WalletState {
|
||||
|
|
@ -157,19 +156,11 @@ internal class WalletLoadingStateFactory(
|
|||
onClick = { clickIntents.onAddFundsClick(userWallet.walletId) },
|
||||
)
|
||||
|
||||
val lastButton = if (isManageFundsEnabled) {
|
||||
WalletManageButton.Transfer(
|
||||
val lastButton = WalletManageButton.Transfer(
|
||||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = { clickIntents.onTransferClick(userWallet.walletId) },
|
||||
)
|
||||
} else {
|
||||
WalletManageButton.Sell(
|
||||
enabled = true,
|
||||
dimContent = false,
|
||||
onClick = { clickIntents.onMultiWalletSellClick(userWalletId = userWallet.walletId) },
|
||||
)
|
||||
}
|
||||
|
||||
return persistentListOf(
|
||||
firstButton,
|
||||
|
|
@ -201,7 +192,6 @@ internal class WalletLoadingStateFactory(
|
|||
},
|
||||
).buttonUM,
|
||||
)
|
||||
if (isManageFundsEnabled) {
|
||||
add(
|
||||
WalletActionButtons.Transfer(
|
||||
isEnabled = false,
|
||||
|
|
@ -210,16 +200,6 @@ internal class WalletLoadingStateFactory(
|
|||
},
|
||||
).buttonUM,
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
WalletActionButtons.Sell(
|
||||
isEnabled = false,
|
||||
onClick = {
|
||||
clickIntents.onMultiWalletSellClick(userWalletId = userWallet.walletId)
|
||||
},
|
||||
).buttonUM,
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -39,7 +38,6 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val polymarketFeatureToggles: PolymarketFeatureToggles,
|
||||
) : BasicAccountListSubscriber() {
|
||||
|
||||
|
|
@ -94,7 +92,7 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingAvailabilityMap = stakingAvailabilityMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
isMultipleCardsEnabled = tangemPayFeatureToggles.isMultipleCardsEnabled,
|
||||
isMultipleCardsEnabled = true,
|
||||
isPolymarketEnabled = polymarketFeatureToggles.isPolymarketEnabled,
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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 dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -21,7 +20,6 @@ internal class SingleWalletSubscriber @AssistedInject constructor(
|
|||
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
override val stateController: WalletStateController,
|
||||
override val clickIntents: WalletClickIntents,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
) : BasicAccountListSubscriber() {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<Unit> = combine(
|
||||
|
|
@ -29,7 +27,7 @@ internal class SingleWalletSubscriber @AssistedInject constructor(
|
|||
flow2 = getAppCurrencyFlow(),
|
||||
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
|
||||
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
|
||||
flow5 = flowOf(tangemPayFeatureToggles.isMultipleCardsEnabled),
|
||||
flow5 = flowOf(true),
|
||||
transform = ::updateState2,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ internal class WalletManagerFactoryCreator @Inject constructor(
|
|||
isSolanaTxHistoryEnabled = true,
|
||||
isSolanaScaledUiAmountEnabled = true,
|
||||
isHederaErc20Enabled = true,
|
||||
isStateOverrideGasEstimateEnabled = featureToggleValues.isStateOverrideGasEstimateEnabled,
|
||||
isStateOverrideGasEstimateEnabled = true,
|
||||
),
|
||||
blockchainDataStorage = blockchainDataStorage,
|
||||
loggers = listOf(blockchainSDKLogger),
|
||||
|
|
@ -51,6 +51,5 @@ internal class WalletManagerFactoryCreator @Inject constructor(
|
|||
|
||||
data class FeatureToggleValues(
|
||||
val isYieldModeSwapEnabled: Boolean,
|
||||
val isStateOverrideGasEstimateEnabled: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -101,9 +101,6 @@ internal object BlockchainSDKFactoryModule {
|
|||
isYieldModeSwapEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.TWI_1326_YIELD_MODE_SWAP_ENABLED,
|
||||
),
|
||||
isStateOverrideGasEstimateEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue