Updated on 2026-08-14
This commit is contained in:
parent
78c4581372
commit
3069b8d32d
23 changed files with 287 additions and 20 deletions
|
|
@ -235,6 +235,11 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data object HighNetworkFee : Warning(
|
||||||
|
title = resourceReference(id = R.string.high_fee_warning_title),
|
||||||
|
subtitle = resourceReference(id = R.string.high_fee_warning_description),
|
||||||
|
)
|
||||||
|
|
||||||
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
||||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,10 @@
|
||||||
"name": "TWI_1469_FOR_YOU_ENABLED",
|
"name": "TWI_1469_FOR_YOU_ENABLED",
|
||||||
"version": "undefined"
|
"version": "undefined"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "TWI_1367_HIGH_FEE_WARNING_ENABLED",
|
||||||
|
"version": "undefined"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "TWI_1638_VA_MVP0_ENABLED",
|
"name": "TWI_1638_VA_MVP0_ENABLED",
|
||||||
"version": "6.1"
|
"version": "6.1"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||||
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
import com.tangem.datasource.utils.mapWithStringKeyTypes
|
||||||
import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase
|
import com.tangem.domain.quotes.GetCurrencyUSDQuoteUseCase
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.quotes.QuotesRepository
|
import com.tangem.domain.quotes.QuotesRepository
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
||||||
import com.tangem.domain.quotes.multi.MultiQuoteUpdater
|
import com.tangem.domain.quotes.multi.MultiQuoteUpdater
|
||||||
|
|
@ -79,4 +80,12 @@ internal object QuotesDataModule {
|
||||||
fun provideGetCurrencyUSDQuoteUseCase(quotesRepository: QuotesRepository): GetCurrencyUSDQuoteUseCase {
|
fun provideGetCurrencyUSDQuoteUseCase(quotesRepository: QuotesRepository): GetCurrencyUSDQuoteUseCase {
|
||||||
return GetCurrencyUSDQuoteUseCase(quotesRepository)
|
return GetCurrencyUSDQuoteUseCase(quotesRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideIsHighNetworkFeeUseCase(
|
||||||
|
getCurrencyUSDQuoteUseCase: GetCurrencyUSDQuoteUseCase,
|
||||||
|
): IsHighNetworkFeeUseCase {
|
||||||
|
return IsHighNetworkFeeUseCase(getCurrencyUSDQuoteUseCase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,4 +6,6 @@ plugins {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.domain.core)
|
api(projects.domain.core)
|
||||||
api(projects.domain.models)
|
api(projects.domain.models)
|
||||||
|
|
||||||
|
testImplementation(projects.test.core)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.tangem.domain.quotes
|
||||||
|
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a network fee is higher than a single hardcoded USD threshold, applied uniformly
|
||||||
|
* across all networks. The fee USD value is computed from the fee currency's USD quote
|
||||||
|
* ([GetCurrencyUSDQuoteUseCase]), independent of the user's selected app currency.
|
||||||
|
*
|
||||||
|
* Returns `false` when there is no USD quote or no raw currency id — never warn without pricing data.
|
||||||
|
*/
|
||||||
|
class IsHighNetworkFeeUseCase(
|
||||||
|
private val getCurrencyUSDQuoteUseCase: GetCurrencyUSDQuoteUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(feeCurrency: CryptoCurrency, feeAmount: BigDecimal): Boolean {
|
||||||
|
val rawCurrencyId = feeCurrency.id.rawCurrencyId ?: return false
|
||||||
|
val usdRate = getCurrencyUSDQuoteUseCase(rawCurrencyId) ?: return false
|
||||||
|
|
||||||
|
return feeAmount.multiply(usdRate) > HIGH_FEE_USD_THRESHOLD
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val HIGH_FEE_USD_THRESHOLD = BigDecimal("10")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.tangem.domain.quotes
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import io.mockk.clearMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal class IsHighNetworkFeeUseCaseTest {
|
||||||
|
|
||||||
|
private val getCurrencyUSDQuoteUseCase: GetCurrencyUSDQuoteUseCase = mockk()
|
||||||
|
private val feeCurrency: CryptoCurrency = mockk()
|
||||||
|
private val rawCurrencyId = CryptoCurrency.RawID("bitcoin")
|
||||||
|
|
||||||
|
private val useCase = IsHighNetworkFeeUseCase(getCurrencyUSDQuoteUseCase)
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
clearMocks(getCurrencyUSDQuoteUseCase, feeCurrency)
|
||||||
|
every { feeCurrency.id.rawCurrencyId } returns rawCurrencyId
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN fee usd value above threshold WHEN invoke THEN returns true`() = runTest {
|
||||||
|
// Arrange — 0.5 coin * 25 USD = 12.5 USD > 10
|
||||||
|
coEvery { getCurrencyUSDQuoteUseCase(rawCurrencyId) } returns BigDecimal("25")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(feeCurrency, BigDecimal("0.5"))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN fee usd value below threshold WHEN invoke THEN returns false`() = runTest {
|
||||||
|
// Arrange — 0.2 coin * 25 USD = 5 USD < 10
|
||||||
|
coEvery { getCurrencyUSDQuoteUseCase(rawCurrencyId) } returns BigDecimal("25")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(feeCurrency, BigDecimal("0.2"))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN fee usd value equal to threshold WHEN invoke THEN returns false`() = runTest {
|
||||||
|
// Arrange — 0.4 coin * 25 USD = 10 USD, not strictly above threshold
|
||||||
|
coEvery { getCurrencyUSDQuoteUseCase(rawCurrencyId) } returns BigDecimal("25")
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(feeCurrency, BigDecimal("0.4"))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no usd quote WHEN invoke THEN returns false`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
coEvery { getCurrencyUSDQuoteUseCase(rawCurrencyId) } returns null
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(feeCurrency, BigDecimal("100"))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no raw currency id WHEN invoke THEN returns false`() = runTest {
|
||||||
|
// Arrange
|
||||||
|
every { feeCurrency.id.rawCurrencyId } returns null
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val result = useCase(feeCurrency, BigDecimal("100"))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
package com.tangem.features.send.api
|
package com.tangem.features.send.api
|
||||||
|
|
||||||
interface SendFeatureToggles
|
interface SendFeatureToggles {
|
||||||
|
val isHighFeeWarningEnabled: Boolean
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
package com.tangem.features.send
|
package com.tangem.features.send
|
||||||
|
|
||||||
|
import com.tangem.core.configtoggle.FeatureToggles
|
||||||
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.features.send.api.SendFeatureToggles
|
import com.tangem.features.send.api.SendFeatureToggles
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSendFeatureToggles @Inject constructor() : SendFeatureToggles
|
internal class DefaultSendFeatureToggles @Inject constructor(
|
||||||
|
private val featureTogglesManager: FeatureTogglesManager,
|
||||||
|
) : SendFeatureToggles {
|
||||||
|
|
||||||
|
override val isHighFeeWarningEnabled: Boolean
|
||||||
|
get() = featureTogglesManager.isFeatureEnabled(
|
||||||
|
toggle = FeatureToggles.TWI_1367_HIGH_FEE_WARNING_ENABLED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.isHotWallet
|
import com.tangem.domain.models.wallet.isHotWallet
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||||
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
||||||
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
|
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
|
||||||
|
|
@ -43,6 +44,7 @@ import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase
|
import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase
|
||||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||||
import com.tangem.domain.utils.convertToSdkAmount
|
import com.tangem.domain.utils.convertToSdkAmount
|
||||||
|
import com.tangem.features.send.api.SendFeatureToggles
|
||||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource
|
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents.SendScreenSource
|
||||||
import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceTrigger
|
import com.tangem.features.send.api.subcomponents.amount.SendAmountReduceTrigger
|
||||||
|
|
@ -113,6 +115,8 @@ internal class SendConfirmModel @Inject constructor(
|
||||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||||
private val currenciesRepository: CurrenciesRepository,
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase,
|
private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase,
|
||||||
|
private val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase,
|
||||||
|
private val sendFeatureToggles: SendFeatureToggles,
|
||||||
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
|
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
|
||||||
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback {
|
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback {
|
||||||
|
|
||||||
|
|
@ -503,6 +507,7 @@ internal class SendConfirmModel @Inject constructor(
|
||||||
|
|
||||||
private fun updateConfirmNotifications() {
|
private fun updateConfirmNotifications() {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
|
val feeCryptoCurrencyStatus = getCurrencyStatusForFeePayment()
|
||||||
notificationsUpdateTrigger.triggerUpdate(
|
notificationsUpdateTrigger.triggerUpdate(
|
||||||
data = NotificationData(
|
data = NotificationData(
|
||||||
destinationAddress = confirmData.enteredDestination.orEmpty(),
|
destinationAddress = confirmData.enteredDestination.orEmpty(),
|
||||||
|
|
@ -512,9 +517,10 @@ internal class SendConfirmModel @Inject constructor(
|
||||||
isIgnoreReduce = confirmData.isIgnoreReduce,
|
isIgnoreReduce = confirmData.isIgnoreReduce,
|
||||||
fee = confirmData.fee,
|
fee = confirmData.fee,
|
||||||
feeError = confirmData.feeError,
|
feeError = confirmData.feeError,
|
||||||
feeCryptoCurrencyStatus = getCurrencyStatusForFeePayment(),
|
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
val isHighNetworkFee = isHighNetworkFee(feeCryptoCurrencyStatus.currency)
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
confirmUM = SendConfirmationNotificationsTransformerV2(
|
confirmUM = SendConfirmationNotificationsTransformerV2(
|
||||||
|
|
@ -524,12 +530,19 @@ internal class SendConfirmModel @Inject constructor(
|
||||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = params.analyticsCategoryName,
|
analyticsCategoryName = params.analyticsCategoryName,
|
||||||
|
isHighNetworkFee = isHighNetworkFee,
|
||||||
).transform(uiState.value.confirmUM),
|
).transform(uiState.value.confirmUM),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun isHighNetworkFee(feeCurrency: CryptoCurrency): Boolean {
|
||||||
|
if (!sendFeatureToggles.isHighFeeWarningEnabled) return false
|
||||||
|
val feeAmount = confirmData.fee?.amount?.value ?: return false
|
||||||
|
return isHighNetworkFeeUseCase(feeCurrency, feeAmount)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
private fun configConfirmNavigation() {
|
private fun configConfirmNavigation() {
|
||||||
combine(
|
combine(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ internal class SendConfirmationNotificationsTransformerV2(
|
||||||
private val cryptoCurrency: CryptoCurrency,
|
private val cryptoCurrency: CryptoCurrency,
|
||||||
private val appCurrency: AppCurrency,
|
private val appCurrency: AppCurrency,
|
||||||
private val analyticsCategoryName: String,
|
private val analyticsCategoryName: String,
|
||||||
|
private val isHighNetworkFee: Boolean = false,
|
||||||
) : Transformer<ConfirmUM> {
|
) : Transformer<ConfirmUM> {
|
||||||
override fun transform(prevState: ConfirmUM): ConfirmUM {
|
override fun transform(prevState: ConfirmUM): ConfirmUM {
|
||||||
val state = prevState as? ConfirmUM.Content ?: return prevState
|
val state = prevState as? ConfirmUM.Content ?: return prevState
|
||||||
|
|
@ -38,10 +39,17 @@ internal class SendConfirmationNotificationsTransformerV2(
|
||||||
notifications = buildList {
|
notifications = buildList {
|
||||||
addTooHighNotification(feeSelectorUM)
|
addTooHighNotification(feeSelectorUM)
|
||||||
addTooLowNotification(feeSelectorUM)
|
addTooLowNotification(feeSelectorUM)
|
||||||
|
addHighNetworkFeeNotification()
|
||||||
}.toPersistentList(),
|
}.toPersistentList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableList<NotificationUM>.addHighNetworkFeeNotification() {
|
||||||
|
if (isHighNetworkFee) {
|
||||||
|
add(NotificationUM.Warning.HighNetworkFee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||||
if (FeeCalculationUtils.checkIfCustomFeeTooLow(feeSelectorUM)) {
|
if (FeeCalculationUtils.checkIfCustomFeeTooLow(feeSelectorUM)) {
|
||||||
add(NotificationUM.Warning.FeeTooLow)
|
add(NotificationUM.Warning.FeeTooLow)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||||
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
|
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
||||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
|
|
@ -30,6 +31,7 @@ import com.tangem.domain.transaction.usecase.gasless.GetFeeForGaslessUseCase
|
||||||
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
|
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.send.api.SendComponent
|
import com.tangem.features.send.api.SendComponent
|
||||||
|
import com.tangem.features.send.api.SendFeatureToggles
|
||||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||||
import com.tangem.features.send.api.entity.PredefinedValues
|
import com.tangem.features.send.api.entity.PredefinedValues
|
||||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadListener
|
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorCheckReloadListener
|
||||||
|
|
@ -122,6 +124,8 @@ internal abstract class SendModelTestBase {
|
||||||
protected val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase = mockk(relaxed = true)
|
protected val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase = mockk(relaxed = true)
|
||||||
protected val currenciesRepository: CurrenciesRepository = mockk(relaxed = true)
|
protected val currenciesRepository: CurrenciesRepository = mockk(relaxed = true)
|
||||||
protected val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase = mockk(relaxed = true)
|
protected val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase = mockk(relaxed = true)
|
||||||
|
protected val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase = mockk(relaxed = true)
|
||||||
|
protected val sendFeatureToggles: SendFeatureToggles = mockk(relaxed = true)
|
||||||
protected val sendAnalyticHelper: SendAnalyticHelper = mockk(relaxed = true)
|
protected val sendAnalyticHelper: SendAnalyticHelper = mockk(relaxed = true)
|
||||||
protected val sendBalanceUpdaterFactory: SendBalanceUpdater.Factory = mockk(relaxed = true)
|
protected val sendBalanceUpdaterFactory: SendBalanceUpdater.Factory = mockk(relaxed = true)
|
||||||
|
|
||||||
|
|
@ -233,6 +237,8 @@ internal abstract class SendModelTestBase {
|
||||||
manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase,
|
manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
createAndSendGaslessTransactionUseCase = createAndSendGaslessTransactionUseCase,
|
createAndSendGaslessTransactionUseCase = createAndSendGaslessTransactionUseCase,
|
||||||
|
isHighNetworkFeeUseCase = isHighNetworkFeeUseCase,
|
||||||
|
sendFeatureToggles = sendFeatureToggles,
|
||||||
sendBalanceUpdaterFactory = sendBalanceUpdaterFactory,
|
sendBalanceUpdaterFactory = sendBalanceUpdaterFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState: ConfirmUM = ConfirmUM.Empty
|
val initialState: ConfirmUM = ConfirmUM.Empty
|
||||||
|
|
||||||
|
|
@ -98,6 +99,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState = createTestConfirmUM()
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
|
@ -120,6 +122,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState = createTestConfirmUM()
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
|
@ -145,6 +148,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState = createTestConfirmUM()
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
|
@ -158,6 +162,31 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
assertThat(content.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN high network fee WHEN transform THEN returns state with high network fee notification`() = runTest {
|
||||||
|
// GIVEN
|
||||||
|
val feeSelectorUM = createNormalFeeSelectorUM()
|
||||||
|
val amountUM = createTestAmountUM()
|
||||||
|
val transformer = SendConfirmationNotificationsTransformerV2(
|
||||||
|
feeSelectorUM = feeSelectorUM,
|
||||||
|
amountUM = amountUM,
|
||||||
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
appCurrency = appCurrency,
|
||||||
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = true,
|
||||||
|
)
|
||||||
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val result = transformer.transform(initialState)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
assertThat(result).isInstanceOf(ConfirmUM.Content::class.java)
|
||||||
|
val content = result as ConfirmUM.Content
|
||||||
|
assertThat(content.notifications).containsExactly(NotificationUM.Warning.HighNetworkFee)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest {
|
fun `GIVEN fee too low WHEN transform THEN returns state with too low notification`() = runTest {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
@ -170,6 +199,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState = createTestConfirmUM()
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
|
@ -196,6 +226,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
appCurrency = appCurrency,
|
appCurrency = appCurrency,
|
||||||
analyticsCategoryName = analyticsCategoryName,
|
analyticsCategoryName = analyticsCategoryName,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
val initialState = createTestConfirmUM()
|
val initialState = createTestConfirmUM()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ package com.tangem.features.swap.v2.api
|
||||||
|
|
||||||
interface SwapFeatureToggles {
|
interface SwapFeatureToggles {
|
||||||
val isSwapProviderFilterEnabled: Boolean
|
val isSwapProviderFilterEnabled: Boolean
|
||||||
|
val isHighFeeWarningEnabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -10,4 +10,7 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
|
||||||
) : SwapFeatureToggles {
|
) : SwapFeatureToggles {
|
||||||
override val isSwapProviderFilterEnabled: Boolean =
|
override val isSwapProviderFilterEnabled: Boolean =
|
||||||
featureToggles.isFeatureEnabled(FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED)
|
featureToggles.isFeatureEnabled(FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED)
|
||||||
|
|
||||||
|
override val isHighFeeWarningEnabled: Boolean =
|
||||||
|
featureToggles.isFeatureEnabled(FeatureToggles.TWI_1367_HIGH_FEE_WARNING_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import com.tangem.domain.models.account.derivationIndex
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.isHotWallet
|
import com.tangem.domain.models.wallet.isHotWallet
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||||
import com.tangem.domain.swap.models.SwapAmountType
|
import com.tangem.domain.swap.models.SwapAmountType
|
||||||
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
||||||
|
|
@ -47,6 +48,7 @@ import com.tangem.features.send.api.subcomponents.destination.entity.Destination
|
||||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||||
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateListener
|
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateListener
|
||||||
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
import com.tangem.features.send.api.subcomponents.notifications.SendNotificationsUpdateTrigger
|
||||||
|
import com.tangem.features.swap.v2.api.SwapFeatureToggles
|
||||||
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
||||||
import com.tangem.features.swap.v2.impl.R
|
import com.tangem.features.swap.v2.impl.R
|
||||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceTrigger
|
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceTrigger
|
||||||
|
|
@ -89,6 +91,8 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
||||||
private val estimateFeeForTokenUseCase: EstimateFeeForTokenUseCase,
|
private val estimateFeeForTokenUseCase: EstimateFeeForTokenUseCase,
|
||||||
private val estimateFeeForGaslessTxUseCase: EstimateFeeForGaslessTxUseCase,
|
private val estimateFeeForGaslessTxUseCase: EstimateFeeForGaslessTxUseCase,
|
||||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||||
|
private val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase,
|
||||||
|
private val swapFeatureToggles: SwapFeatureToggles,
|
||||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||||
private val sendNotificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
private val sendNotificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||||
private val swapNotificationsUpdateTrigger: SwapNotificationsUpdateTrigger,
|
private val swapNotificationsUpdateTrigger: SwapNotificationsUpdateTrigger,
|
||||||
|
|
@ -467,12 +471,19 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
||||||
feeValue = confirmData.fee?.amount?.value,
|
feeValue = confirmData.fee?.amount?.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
val isHighNetworkFee = isHighNetworkFee(feeCryptoCurrencyStatus.currency)
|
||||||
uiState.transformerUpdate(
|
uiState.transformerUpdate(
|
||||||
SendWithSwapConfirmationNotificationsTransformer(),
|
SendWithSwapConfirmationNotificationsTransformer(isHighNetworkFee = isHighNetworkFee),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun isHighNetworkFee(feeCurrency: CryptoCurrency): Boolean {
|
||||||
|
if (!swapFeatureToggles.isHighFeeWarningEnabled) return false
|
||||||
|
val feeAmount = confirmData.fee?.amount?.value ?: return false
|
||||||
|
return isHighNetworkFeeUseCase(feeCurrency, feeAmount)
|
||||||
|
}
|
||||||
|
|
||||||
private fun subscribeOnNotificationUpdates() {
|
private fun subscribeOnNotificationUpdates() {
|
||||||
combine(
|
combine(
|
||||||
flow = sendNotificationsUpdateListener.hasErrorFlow,
|
flow = sendNotificationsUpdateListener.hasErrorFlow,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
|
||||||
internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<SendWithSwapUM> {
|
internal class SendWithSwapConfirmationNotificationsTransformer(
|
||||||
|
private val isHighNetworkFee: Boolean,
|
||||||
|
) : Transformer<SendWithSwapUM> {
|
||||||
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
|
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
|
||||||
val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState
|
val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState
|
||||||
val feeSelectorUM = prevState.feeSelectorUM as? FeeSelectorUM.Content ?: return prevState
|
val feeSelectorUM = prevState.feeSelectorUM as? FeeSelectorUM.Content ?: return prevState
|
||||||
|
|
@ -34,11 +36,18 @@ internal class SendWithSwapConfirmationNotificationsTransformer : Transformer<Se
|
||||||
notifications = buildList {
|
notifications = buildList {
|
||||||
addTooHighNotification(feeSelectorUM = feeSelectorUM)
|
addTooHighNotification(feeSelectorUM = feeSelectorUM)
|
||||||
addTooLowNotification(feeSelectorUM = feeSelectorUM)
|
addTooLowNotification(feeSelectorUM = feeSelectorUM)
|
||||||
|
addHighNetworkFeeNotification()
|
||||||
}.toPersistentList(),
|
}.toPersistentList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableList<NotificationUM>.addHighNetworkFeeNotification() {
|
||||||
|
if (isHighNetworkFee) {
|
||||||
|
add(NotificationUM.Warning.HighNetworkFee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
private fun MutableList<NotificationUM>.addTooLowNotification(feeSelectorUM: FeeSelectorUM.Content) {
|
||||||
if (checkIfCustomFeeTooLow(feeSelectorUM = feeSelectorUM)) {
|
if (checkIfCustomFeeTooLow(feeSelectorUM = feeSelectorUM)) {
|
||||||
add(NotificationUM.Warning.FeeTooLow)
|
add(NotificationUM.Warning.FeeTooLow)
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ interface SwapFeatureToggles {
|
||||||
val isSwapPredefinedButtonsEnabled: Boolean
|
val isSwapPredefinedButtonsEnabled: Boolean
|
||||||
val isExpressShareButtonEnabled: Boolean
|
val isExpressShareButtonEnabled: Boolean
|
||||||
val isSwapBestDexRateEnabled: Boolean
|
val isSwapBestDexRateEnabled: Boolean
|
||||||
|
val isHighFeeWarningEnabled: Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -52,4 +52,9 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
|
||||||
get() = featureTogglesManager.isFeatureEnabled(
|
get() = featureTogglesManager.isFeatureEnabled(
|
||||||
toggle = FeatureToggles.AND_15715_SWAP_BEST_DEX_RATE_ENABLED,
|
toggle = FeatureToggles.AND_15715_SWAP_BEST_DEX_RATE_ENABLED,
|
||||||
) && isSwapIntegratedApproveEnabled
|
) && isSwapIntegratedApproveEnabled
|
||||||
|
|
||||||
|
override val isHighFeeWarningEnabled: Boolean
|
||||||
|
get() = featureTogglesManager.isFeatureEnabled(
|
||||||
|
toggle = FeatureToggles.TWI_1367_HIGH_FEE_WARNING_ENABLED,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +65,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.models.wallet.isHotWallet
|
import com.tangem.domain.models.wallet.isHotWallet
|
||||||
import com.tangem.domain.pay.WithdrawalResult
|
import com.tangem.domain.pay.WithdrawalResult
|
||||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||||
|
|
@ -173,6 +174,7 @@ internal class SwapModel @Inject constructor(
|
||||||
private val getSwapUiModeUseCase: GetSwapUiModeUseCase,
|
private val getSwapUiModeUseCase: GetSwapUiModeUseCase,
|
||||||
private val setSwapUiModeUseCase: SetSwapUiModeUseCase,
|
private val setSwapUiModeUseCase: SetSwapUiModeUseCase,
|
||||||
private val calculateAmountUseCase: CalculateAmountUseCase,
|
private val calculateAmountUseCase: CalculateAmountUseCase,
|
||||||
|
private val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<SwapComponent.Params>()
|
private val params = paramsContainer.require<SwapComponent.Params>()
|
||||||
|
|
@ -1103,7 +1105,7 @@ internal class SwapModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLoadedState(
|
private suspend fun setupLoadedState(
|
||||||
provider: SwapProvider,
|
provider: SwapProvider,
|
||||||
state: SwapState,
|
state: SwapState,
|
||||||
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
||||||
|
|
@ -1127,7 +1129,7 @@ internal class SwapModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupQuotesLoadedUiState(provider: SwapProvider, state: SwapState.QuotesLoadedState) {
|
private suspend fun setupQuotesLoadedUiState(provider: SwapProvider, state: SwapState.QuotesLoadedState) {
|
||||||
val loadedStates = dataState.getLastLoadedSuccessStates()
|
val loadedStates = dataState.getLastLoadedSuccessStates()
|
||||||
val additionalBadge = SwapProviderResolver.resolveBadge(
|
val additionalBadge = SwapProviderResolver.resolveBadge(
|
||||||
provider = provider,
|
provider = provider,
|
||||||
|
|
@ -1136,17 +1138,26 @@ internal class SwapModel @Inject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
|
||||||
)
|
)
|
||||||
|
val swapFee = getSelectedSwapFee()
|
||||||
uiState = stateBuilder.createQuotesLoadedState(
|
uiState = stateBuilder.createQuotesLoadedState(
|
||||||
uiStateHolder = uiState,
|
uiStateHolder = uiState,
|
||||||
quoteModel = state,
|
quoteModel = state,
|
||||||
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
|
||||||
swapProvider = provider,
|
swapProvider = provider,
|
||||||
additionalBadge = additionalBadge,
|
additionalBadge = additionalBadge,
|
||||||
swapFee = getSelectedSwapFee(),
|
swapFee = swapFee,
|
||||||
feeError = feeSelectorRepository.state.value as? FeeSelectorUM.Error,
|
feeError = feeSelectorRepository.state.value as? FeeSelectorUM.Error,
|
||||||
|
isHighNetworkFee = isHighNetworkFee(swapFee),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun isHighNetworkFee(swapFee: SwapFee?): Boolean {
|
||||||
|
if (!swapFeatureToggles.isHighFeeWarningEnabled) return false
|
||||||
|
swapFee ?: return false
|
||||||
|
val feeAmount = swapFee.fee.amount.value ?: return false
|
||||||
|
return isHighNetworkFeeUseCase(swapFee.selectedFeeToken.currency, feeAmount)
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendAnalyticsForNotifications(
|
private fun sendAnalyticsForNotifications(
|
||||||
provider: SwapProvider,
|
provider: SwapProvider,
|
||||||
fromToken: CryptoCurrencyStatus,
|
fromToken: CryptoCurrencyStatus,
|
||||||
|
|
@ -2077,6 +2088,7 @@ internal class SwapModel @Inject constructor(
|
||||||
}
|
}
|
||||||
analyticsEventHandler.send(SwapEvents.ProviderChosen(provider))
|
analyticsEventHandler.send(SwapEvents.ProviderChosen(provider))
|
||||||
uiState = stateBuilder.dismissBottomSheet(uiState)
|
uiState = stateBuilder.dismissBottomSheet(uiState)
|
||||||
|
modelScope.launch {
|
||||||
setupLoadedState(
|
setupLoadedState(
|
||||||
provider = provider,
|
provider = provider,
|
||||||
state = swapState,
|
state = swapState,
|
||||||
|
|
@ -2084,6 +2096,7 @@ internal class SwapModel @Inject constructor(
|
||||||
toSwapCurrencyStatus = toSwapCurrencyStatus,
|
toSwapCurrencyStatus = toSwapCurrencyStatus,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onProviderFilterSelect = { filterType ->
|
onProviderFilterSelect = { filterType ->
|
||||||
analyticsEventHandler.send(
|
analyticsEventHandler.send(
|
||||||
|
|
@ -2823,12 +2836,14 @@ internal class SwapModel @Inject constructor(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
modelScope.launch {
|
||||||
setupLoadedState(
|
setupLoadedState(
|
||||||
provider = provider,
|
provider = provider,
|
||||||
state = swapState,
|
state = swapState,
|
||||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||||
toSwapCurrencyStatus = toSwapCurrencyStatus,
|
toSwapCurrencyStatus = toSwapCurrencyStatus,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
TangemLogger.e("loadFee: ${feeError.error}, isHidden = true")
|
TangemLogger.e("loadFee: ${feeError.error}, isHidden = true")
|
||||||
refreshTransferUIStateIfNeeded()
|
refreshTransferUIStateIfNeeded()
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ internal class SwapNotificationsFactory(
|
||||||
swapFee: SwapFee?,
|
swapFee: SwapFee?,
|
||||||
feeError: GetFeeError?,
|
feeError: GetFeeError?,
|
||||||
appRouter: AppRouter,
|
appRouter: AppRouter,
|
||||||
|
isHighNetworkFee: Boolean = false,
|
||||||
): ImmutableList<NotificationUM> {
|
): ImmutableList<NotificationUM> {
|
||||||
val warnings = buildList {
|
val warnings = buildList {
|
||||||
maybeAddFeeErrorNotification(feeCryptoCurrencyStatus, quoteModel, feeError)
|
maybeAddFeeErrorNotification(feeCryptoCurrencyStatus, quoteModel, feeError)
|
||||||
|
|
@ -135,10 +136,17 @@ internal class SwapNotificationsFactory(
|
||||||
maybeAddUnableCoverFeeWarning(quoteModel, feeCryptoCurrencyStatus, appRouter)
|
maybeAddUnableCoverFeeWarning(quoteModel, feeCryptoCurrencyStatus, appRouter)
|
||||||
maybeAddTransactionInProgressWarning(quoteModel)
|
maybeAddTransactionInProgressWarning(quoteModel)
|
||||||
maybeAddPriceImpactNotification(quoteModel.priceImpact)
|
maybeAddPriceImpactNotification(quoteModel.priceImpact)
|
||||||
|
maybeAddHighNetworkFeeWarning(isHighNetworkFee)
|
||||||
}
|
}
|
||||||
return warnings.toPersistentList()
|
return warnings.toPersistentList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableList<NotificationUM>.maybeAddHighNetworkFeeWarning(isHighNetworkFee: Boolean) {
|
||||||
|
if (isHighNetworkFee) {
|
||||||
|
add(NotificationUM.Warning.HighNetworkFee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MutableList<NotificationUM>.maybeAddRentExemptionError(quoteModel: SwapState.QuotesLoadedState) {
|
private fun MutableList<NotificationUM>.maybeAddRentExemptionError(quoteModel: SwapState.QuotesLoadedState) {
|
||||||
quoteModel.currencyCheck?.rentWarning?.let {
|
quoteModel.currencyCheck?.rentWarning?.let {
|
||||||
add(NotificationUM.Solana.RentInfo(it))
|
add(NotificationUM.Solana.RentInfo(it))
|
||||||
|
|
|
||||||
|
|
@ -577,6 +577,7 @@ internal class StateBuilder(
|
||||||
additionalBadge: ProviderState.AdditionalBadge,
|
additionalBadge: ProviderState.AdditionalBadge,
|
||||||
swapFee: SwapFee?,
|
swapFee: SwapFee?,
|
||||||
feeError: FeeSelectorUM.Error?,
|
feeError: FeeSelectorUM.Error?,
|
||||||
|
isHighNetworkFee: Boolean,
|
||||||
): SwapStateHolder {
|
): SwapStateHolder {
|
||||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||||
|
|
@ -590,6 +591,7 @@ internal class StateBuilder(
|
||||||
swapFee = swapFee,
|
swapFee = swapFee,
|
||||||
feeError = feeError?.error,
|
feeError = feeError?.error,
|
||||||
appRouter = appRouter,
|
appRouter = appRouter,
|
||||||
|
isHighNetworkFee = isHighNetworkFee,
|
||||||
)
|
)
|
||||||
|
|
||||||
val fromAccountTitleUM = when {
|
val fromAccountTitleUM = when {
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ internal class StateBuilderSwapButtonTest {
|
||||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||||
swapFee = null,
|
swapFee = null,
|
||||||
feeError = null,
|
feeError = null,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(result.swapButton.isEnabled).isTrue()
|
assertThat(result.swapButton.isEnabled).isTrue()
|
||||||
|
|
@ -134,6 +135,7 @@ internal class StateBuilderSwapButtonTest {
|
||||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||||
swapFee = null,
|
swapFee = null,
|
||||||
feeError = null,
|
feeError = null,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(result.swapButton.isEnabled).isFalse()
|
assertThat(result.swapButton.isEnabled).isFalse()
|
||||||
|
|
@ -163,6 +165,7 @@ internal class StateBuilderSwapButtonTest {
|
||||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||||
swapFee = null,
|
swapFee = null,
|
||||||
feeError = null,
|
feeError = null,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(result.swapButton.isEnabled).isFalse()
|
assertThat(result.swapButton.isEnabled).isFalse()
|
||||||
|
|
@ -187,6 +190,7 @@ internal class StateBuilderSwapButtonTest {
|
||||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||||
swapFee = buildSwapFee(),
|
swapFee = buildSwapFee(),
|
||||||
feeError = null,
|
feeError = null,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(result.swapButton.isEnabled).isTrue()
|
assertThat(result.swapButton.isEnabled).isTrue()
|
||||||
|
|
@ -215,6 +219,7 @@ internal class StateBuilderSwapButtonTest {
|
||||||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||||
swapFee = buildSwapFee(),
|
swapFee = buildSwapFee(),
|
||||||
feeError = null,
|
feeError = null,
|
||||||
|
isHighNetworkFee = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(result.swapButton.isEnabled).isFalse()
|
assertThat(result.swapButton.isEnabled).isFalse()
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||||
|
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
|
||||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||||
import com.tangem.domain.stories.ShouldShowStoriesUseCase
|
import com.tangem.domain.stories.ShouldShowStoriesUseCase
|
||||||
|
|
@ -103,6 +104,7 @@ internal abstract class SwapModelTestBase {
|
||||||
protected val getSwapUiModeUseCase: GetSwapUiModeUseCase = mockk(relaxed = true)
|
protected val getSwapUiModeUseCase: GetSwapUiModeUseCase = mockk(relaxed = true)
|
||||||
protected val setSwapUiModeUseCase: SetSwapUiModeUseCase = mockk(relaxed = true)
|
protected val setSwapUiModeUseCase: SetSwapUiModeUseCase = mockk(relaxed = true)
|
||||||
protected val calculateAmountUseCase: CalculateAmountUseCase = mockk(relaxed = true)
|
protected val calculateAmountUseCase: CalculateAmountUseCase = mockk(relaxed = true)
|
||||||
|
protected val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase = mockk(relaxed = true)
|
||||||
protected val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase = mockk(relaxed = true)
|
protected val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase = mockk(relaxed = true)
|
||||||
protected val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase = mockk(relaxed = true)
|
protected val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase = mockk(relaxed = true)
|
||||||
|
|
||||||
|
|
@ -175,6 +177,7 @@ internal abstract class SwapModelTestBase {
|
||||||
getSwapUiModeUseCase = getSwapUiModeUseCase,
|
getSwapUiModeUseCase = getSwapUiModeUseCase,
|
||||||
setSwapUiModeUseCase = setSwapUiModeUseCase,
|
setSwapUiModeUseCase = setSwapUiModeUseCase,
|
||||||
calculateAmountUseCase = calculateAmountUseCase,
|
calculateAmountUseCase = calculateAmountUseCase,
|
||||||
|
isHighNetworkFeeUseCase = isHighNetworkFeeUseCase,
|
||||||
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
|
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
|
||||||
sendBackupProblemEmailUseCase = sendBackupProblemEmailUseCase,
|
sendBackupProblemEmailUseCase = sendBackupProblemEmailUseCase,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue