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