Updated on 2026-08-14
This commit is contained in:
commit
0b78c692ee
19 changed files with 239 additions and 74 deletions
|
|
@ -3,7 +3,6 @@ package com.tangem.scenarios
|
|||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.extensions.SwipeDirection
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.extensions.swipeMarketsBlock
|
||||
import com.tangem.common.extensions.swipeVertical
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onMarketsExchangesScreen
|
||||
|
|
@ -55,7 +54,7 @@ fun BaseTestCase.openMarketsScreen() {
|
|||
synchronizeAddresses()
|
||||
}
|
||||
step("Open 'Markets' screen") {
|
||||
swipeMarketsBlock(SwipeDirection.UP)
|
||||
onMainScreen { searchThroughMarketPlaceholder.performClick() }
|
||||
waitForIdle()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class TotalBalanceUpdateTest : BaseTestCase() {
|
|||
onMainScreen { totalBalanceText.assertTextContains(TOTAL_BALANCE) }
|
||||
}
|
||||
step("Open 'Markets screen'") {
|
||||
swipeMarketsBlock(SwipeDirection.UP)
|
||||
onMainScreen { searchThroughMarketPlaceholder.performClick() }
|
||||
waitForIdle()
|
||||
}
|
||||
step("Click on $tokenTitle token") {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.scenarios.assertMarketsExchangesScreen
|
|||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openMarketsExchangesScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.screens.onMainScreen
|
||||
import com.tangem.screens.onMarketsExchangesScreen
|
||||
import com.tangem.screens.onMarketsScreen
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
|
|
@ -52,7 +53,7 @@ class MarketsExchangesTest : BaseTestCase() {
|
|||
synchronizeAddresses()
|
||||
}
|
||||
step("Open 'Markets' screen") {
|
||||
swipeMarketsBlock(SwipeDirection.UP)
|
||||
onMainScreen { searchThroughMarketPlaceholder.performClick() }
|
||||
waitForIdle()
|
||||
}
|
||||
step("Click on '$tokenName' token") {
|
||||
|
|
|
|||
|
|
@ -25,9 +25,8 @@ class AnalyticsChain(
|
|||
val interceptor = CardContextInterceptor(previousChainResult)
|
||||
val params = event.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
event.params = params.toMap()
|
||||
|
||||
Analytics.send(event)
|
||||
Analytics.send(event.withParams(params.toMap()))
|
||||
|
||||
return previousChainResult.right()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,9 +238,8 @@ internal class DefaultTangemSdkManager(
|
|||
val interceptor = CardContextInterceptor(scanResponse)
|
||||
val params = analyticsEvent.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
analyticsEvent.params = params.toMap()
|
||||
|
||||
Analytics.send(event = analyticsEvent)
|
||||
Analytics.send(event = analyticsEvent.withParams(params.toMap()))
|
||||
}
|
||||
.doOnFailure { tangemError ->
|
||||
(tangemError as? TangemSdkError)?.let { error ->
|
||||
|
|
|
|||
|
|
@ -170,6 +170,14 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
subtitle = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
|
||||
data class NetworkAccountNotFunded(val coinName: String) : Error(
|
||||
title = resourceReference(R.string.alert_failed_to_send_transaction_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.no_account_generic,
|
||||
formatArgs = wrappedList(coinName),
|
||||
),
|
||||
)
|
||||
|
||||
data object DestinationTagRequired : Error(
|
||||
title = resourceReference(id = R.string.send_validation_destination_tag_required_title),
|
||||
subtitle = resourceReference(id = R.string.send_validation_destination_tag_required_description),
|
||||
|
|
|
|||
|
|
@ -136,14 +136,24 @@ object NotificationsFactory {
|
|||
// No need to show reserve amount warning if fee currency is unknown for token transfer
|
||||
return
|
||||
} else if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingCoinAmount) {
|
||||
add(
|
||||
NotificationUM.Error.ReserveAmount(
|
||||
reserveAmount.format {
|
||||
crypto(feeCryptoCurrency ?: cryptoCurrency)
|
||||
},
|
||||
),
|
||||
)
|
||||
// account not funded, sending coin amount < reserve (send coin with less amount OR send any token)
|
||||
|
||||
if (cryptoCurrency is CryptoCurrency.Coin) {
|
||||
// Try to send coin amount less than reserve amount (e.g. less than 1 XLM in Stellar)
|
||||
add(
|
||||
NotificationUM.Error.ReserveAmount(
|
||||
reserveAmount.format {
|
||||
crypto(feeCryptoCurrency ?: cryptoCurrency)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
checkNotNull(feeCryptoCurrency)
|
||||
// Try to send any token (e.g. USDC in Stellar, but account not funded -> user must send XLM at first)
|
||||
add(NotificationUM.Error.NetworkAccountNotFunded(coinName = feeCryptoCurrency.name))
|
||||
}
|
||||
}
|
||||
// TODO: check the RECEIVER account trustline before sending
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addMinimumAmountErrorNotification(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,31 @@ package com.tangem.core.analytics.models
|
|||
open class AnalyticsEvent(
|
||||
val category: String,
|
||||
val event: String,
|
||||
var params: Map<String, String> = mapOf(),
|
||||
val params: Map<String, String> = mapOf(),
|
||||
) {
|
||||
|
||||
val id: String = "[$category] $event"
|
||||
|
||||
fun withParams(newParams: Map<String, String>) = AnalyticsEvent(category, event, newParams)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as AnalyticsEvent
|
||||
|
||||
if (category != other.category) return false
|
||||
if (event != other.event) return false
|
||||
if (params != other.params) return false
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = category.hashCode()
|
||||
result = 31 * result + event.hashCode()
|
||||
result = 31 * result + params.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -91,16 +91,16 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
if (event is OneTimePerSessionEvent && !shouldSendThrottledEvent(event)) {
|
||||
return@launch
|
||||
}
|
||||
event.params = applyParamsInterceptors(event)
|
||||
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(event) }
|
||||
val eventWithParams = event.withParams(applyParamsInterceptors(event))
|
||||
val eventFilter = analyticsFilters.firstOrNull { it.canBeAppliedTo(eventWithParams) }
|
||||
|
||||
analyticsMutex.withLock {
|
||||
when {
|
||||
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(event) }
|
||||
eventFilter.canBeSent(event) -> {
|
||||
eventFilter == null -> analyticsHandlers.forEach { handler -> handler.send(eventWithParams) }
|
||||
eventFilter.canBeSent(eventWithParams) -> {
|
||||
analyticsHandlers
|
||||
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, event) }
|
||||
.forEach { handler -> handler.send(event) }
|
||||
.filter { handler -> eventFilter.canBeConsumedByHandler(handler, eventWithParams) }
|
||||
.forEach { handler -> handler.send(eventWithParams) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,10 +109,10 @@ object Analytics : GlobalAnalyticsEventHandler {
|
|||
|
||||
override fun sendErrorEvent(event: AnalyticsEvent) {
|
||||
analyticsScope.launch {
|
||||
event.params = applyParamsInterceptors(event)
|
||||
val eventWithParams = event.withParams(applyParamsInterceptors(event))
|
||||
analyticsMutex.withLock {
|
||||
analyticsHandlers.filterIsInstance<AnalyticsErrorHandler>()
|
||||
.forEach { handler -> handler.sendErrorEvent(event) }
|
||||
.forEach { handler -> handler.sendErrorEvent(eventWithParams) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,6 +242,33 @@ fun BigDecimalFiatFormat.anyDecimals(decimals: Int): BigDecimalFormat = BigDecim
|
|||
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats fiat amount following the pattern:
|
||||
*
|
||||
* 123 -> $123
|
||||
*
|
||||
* 123.1 -> $123.10
|
||||
*
|
||||
* 123.10 -> $123.10
|
||||
*
|
||||
* 123.456 -> $123.46
|
||||
*/
|
||||
fun BigDecimalFiatFormat.optionalDecimals(): BigDecimalFormat = BigDecimalFormat { value ->
|
||||
val formatterCurrency = getJavaCurrencyByCode(fiatCurrencyCode)
|
||||
|
||||
val hasFraction = value.stripTrailingZeros().scale() > 0
|
||||
val defaultDigits = formatterCurrency.defaultFractionDigits
|
||||
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
|
||||
currency = formatterCurrency
|
||||
minimumFractionDigits = if (hasFraction) defaultDigits else 0
|
||||
maximumFractionDigits = defaultDigits
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
}
|
||||
|
||||
formatter.format(value)
|
||||
.replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol)
|
||||
}
|
||||
|
||||
// == Helpers ==
|
||||
|
||||
private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.tangem.core.ui.format.bigdecimal
|
|||
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -342,4 +345,37 @@ internal class BigDecimalFiatFormatTest {
|
|||
Truth.assertThat(formatted)
|
||||
.isEqualTo("-" + "0.50".addUsdSymbolLeft())
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestCasesForOptionalDecimals")
|
||||
fun `GIVEN amount WHEN format with optionalDecimals THEN correct answer`(
|
||||
amount: String,
|
||||
answer: String,
|
||||
) {
|
||||
val testValue = BigDecimal(amount)
|
||||
|
||||
val formatted = testValue.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = usdCurrencyCode,
|
||||
fiatCurrencySymbol = usdSymbol,
|
||||
locale = testLocale,
|
||||
).optionalDecimals()
|
||||
}
|
||||
|
||||
Truth.assertThat(formatted).isEqualTo(answer)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@JvmStatic
|
||||
fun provideTestCasesForOptionalDecimals() = listOf(
|
||||
Arguments.of("123", "$123"),
|
||||
Arguments.of("123.1", "$123.10"),
|
||||
Arguments.of("123.10", "$123.10"),
|
||||
Arguments.of("123.456", "$123.46"),
|
||||
Arguments.of("0", "$0"),
|
||||
Arguments.of("0.1", "$0.10"),
|
||||
Arguments.of("0.12", "$0.12"),
|
||||
Arguments.of("0.127", "$0.13"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -194,20 +194,36 @@ sealed class TangemPayAnalyticsEvents(
|
|||
)
|
||||
|
||||
class ReplaceCardClicked : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Replace Card Clicked",
|
||||
)
|
||||
|
||||
class ReplaceCardConfirmationPopupOpened : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Replace Card Confirmation Popup Opened",
|
||||
)
|
||||
|
||||
class ReplaceCardConfirmed : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Replace Card Confirmed",
|
||||
)
|
||||
|
||||
class LimitChangeClicked : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Daily Limit Change Clicked",
|
||||
)
|
||||
|
||||
class LimitManagementOpened : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Limit Management Screen Opened",
|
||||
)
|
||||
|
||||
data class LimitChangeConfirmed(val amount: String) : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Card Management",
|
||||
event = "Visa Set Limits Confirmed",
|
||||
params = mapOf("amount" to amount),
|
||||
)
|
||||
|
||||
class MainVisaPermanentBannerClicked : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Onboarding",
|
||||
event = "Visa Permanent Banner Clicked",
|
||||
|
|
|
|||
|
|
@ -1223,6 +1223,23 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
val fee = when (txFeeSealedState) {
|
||||
is TxFeeSealedState.Component -> txFeeSealedState.txFee.fee.amount.value
|
||||
is TxFeeSealedState.Legacy -> {
|
||||
when (val txFee = txFeeSealedState.txFeeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.fee.amount.value
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.fee.amount.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val feeState = getFeeState(
|
||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||
fee = fee,
|
||||
spendAmount = amount,
|
||||
)
|
||||
|
||||
when (provider.type) {
|
||||
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
|
||||
val state = updatePermissionState(
|
||||
|
|
@ -1236,26 +1253,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
state.copy(
|
||||
preparedSwapConfigState = state.preparedSwapConfigState.copy(
|
||||
isBalanceEnough = isBalanceWithoutFeeEnough,
|
||||
feeState = feeState,
|
||||
),
|
||||
)
|
||||
}
|
||||
ExchangeProviderType.CEX -> {
|
||||
val fee = when (txFeeSealedState) {
|
||||
is TxFeeSealedState.Component -> txFeeSealedState.txFee.fee.amount.value
|
||||
is TxFeeSealedState.Legacy -> {
|
||||
when (val txFee = txFeeSealedState.txFeeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> txFee.priorityFee.fee.amount.value
|
||||
is TxFeeState.SingleFeeState -> txFee.fee.fee.amount.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val feeState = getFeeState(
|
||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||
fee = fee,
|
||||
spendAmount = amount,
|
||||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
preparedSwapConfigState = PreparedSwapConfigState(
|
||||
|
|
|
|||
|
|
@ -302,23 +302,24 @@ internal class SwapNotificationsFactory(
|
|||
) {
|
||||
if (hideFee) return
|
||||
val fromCurrency = quoteModel.fromTokenInfo.swapCurrencyStatus.currency
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough ?: return
|
||||
val shouldShowCoverWarning = quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
val feeEnoughState = quoteModel.preparedSwapConfigState.feeState as? SwapFeeState.NotEnough
|
||||
val shouldShowCoverWarning = !quoteModel.preparedSwapConfigState.isBalanceEnough &&
|
||||
quoteModel.permissionState !is PermissionDataState.PermissionLoading &&
|
||||
feeCryptoCurrencyStatus?.currency != fromCurrency
|
||||
|
||||
val isNotEnoughFee =
|
||||
val isCEXProvider = quoteModel.swapProvider.type == ExchangeProviderType.CEX
|
||||
|
||||
val isNotEnoughFee = feeEnoughState is SwapFeeState.NotEnough && !isCEXProvider ||
|
||||
quoteModel.preparedSwapConfigState.includeFeeInAmount is IncludeFeeInAmount.BalanceNotEnough
|
||||
|
||||
val isGaslessAvailable = isGaslessFeeSupportedForNetwork(fromCurrency.network) &&
|
||||
quoteModel.swapProvider.type == ExchangeProviderType.CEX
|
||||
val isGaslessAvailable = isGaslessFeeSupportedForNetwork(fromCurrency.network) && isCEXProvider
|
||||
if (shouldShowCoverWarning && !isGaslessAvailable || isNotEnoughFee) {
|
||||
add(
|
||||
SwapNotificationUM.Error.UnableToCoverFeeWarning(
|
||||
fromToken = fromCurrency,
|
||||
feeCurrency = feeCryptoCurrencyStatus?.currency,
|
||||
currencyName = feeEnoughState.currencyName ?: fromCurrency.network.name,
|
||||
currencySymbol = feeEnoughState.currencySymbol ?: fromCurrency.network.currencySymbol,
|
||||
currencyName = feeEnoughState?.currencyName ?: fromCurrency.network.name,
|
||||
currencySymbol = feeEnoughState?.currencySymbol ?: fromCurrency.network.currencySymbol,
|
||||
onConfirmClick = actions.onBuyClick,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -382,8 +382,12 @@ private fun Content(
|
|||
onFocusChange = type.onFocusChanged,
|
||||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
LaunchedEffect(type.isEnabled) {
|
||||
if (type.isEnabled) {
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
focusRequester.freeFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.tangempay.limit.setup
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -8,10 +9,10 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.WrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.anyDecimals
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
|
||||
import com.tangem.core.ui.format.bigdecimal.optionalDecimals
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.domain.models.account.requireCardWithId
|
|||
import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute
|
||||
|
|
@ -34,6 +36,7 @@ import java.math.BigDecimal
|
|||
import java.util.Currency
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayCardLimitSetupModel @Inject constructor(
|
||||
|
|
@ -43,6 +46,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
private val setTangemPayCardLimitUseCase: SetTangemPayCardLimitUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
|
||||
|
|
@ -70,6 +74,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
)
|
||||
|
||||
init {
|
||||
analytics.send(TangemPayAnalyticsEvents.LimitManagementOpened())
|
||||
observeCardState()
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +139,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
val amount = uiState.value.amountFieldModel.value.toBigDecimalOrNull() ?: return
|
||||
modelScope.launch {
|
||||
uiState.update { it.copy(isSubmitButtonLoading = true) }
|
||||
analytics.send(TangemPayAnalyticsEvents.LimitChangeConfirmed(amount.toPlainString()))
|
||||
setTangemPayCardLimitUseCase(
|
||||
cardId = cardId,
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -168,7 +174,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
id = R.string.tangempay_card_limit_setup_amount_subtitle,
|
||||
formatArgs = WrappedList(
|
||||
listOf(
|
||||
MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) },
|
||||
MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -177,8 +183,8 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
id = R.string.tangempay_daily_limit_hint,
|
||||
formatArgs = WrappedList(
|
||||
listOf(
|
||||
MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) },
|
||||
maxLimit.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) },
|
||||
MIN_LIMIT.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() },
|
||||
maxLimit.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -191,7 +197,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
BigDecimal("10000"),
|
||||
BigDecimal("25000"),
|
||||
).map { preset ->
|
||||
val label = preset.format { fiat(currency.currencyCode, currency.symbol).anyDecimals(0) }
|
||||
val label = preset.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() }
|
||||
TangemPayCardLimitSetupUM.LimitPresetUM(
|
||||
label = label,
|
||||
onClick = { onPresetClick(preset) },
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
|
||||
import com.tangem.core.ui.format.bigdecimal.optionalDecimals
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
|
|
@ -104,9 +105,9 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
TangemPayDailyLimitBlockState.Content(
|
||||
limit = limit.amount.format {
|
||||
val symbol = getJavaCurrencyByCode(status.currencyCode).symbol
|
||||
fiat(status.currencyCode, symbol)
|
||||
fiat(status.currencyCode, symbol).optionalDecimals()
|
||||
},
|
||||
onChangeClick = { router.push(TangemPayCardDetailsInnerRoute.LimitSetup) },
|
||||
onChangeClick = ::onClickLimitChange,
|
||||
)
|
||||
} else {
|
||||
TangemPayDailyLimitBlockState.Error
|
||||
|
|
@ -138,6 +139,11 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun onClickLimitChange() {
|
||||
analytics.send(TangemPayAnalyticsEvents.LimitChangeClicked())
|
||||
router.push(TangemPayCardDetailsInnerRoute.LimitSetup)
|
||||
}
|
||||
|
||||
private fun onClickChangePIN(isPinSet: Boolean) {
|
||||
if (!isPinSet) {
|
||||
router.push(TangemPayCardDetailsInnerRoute.ChangePIN)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.tangempay.limit.setup
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.model.MutableParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
|
|
@ -15,20 +16,20 @@ import com.tangem.domain.models.pay.TangemPayCardLimitPeriod
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.pay.usecase.SetTangemPayCardLimitUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TangemPayCardLimitSetupModelTest {
|
||||
|
||||
private val cardId = "test_card_id"
|
||||
|
|
@ -38,6 +39,7 @@ internal class TangemPayCardLimitSetupModelTest {
|
|||
private val uiMessageSender: UiMessageSender = mockk(relaxed = true)
|
||||
private val setLimitUseCase: SetTangemPayCardLimitUseCase = mockk(relaxed = true)
|
||||
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier = mockk()
|
||||
private val analytics: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
|
||||
private val initialCard = TangemPayCard(
|
||||
id = cardId,
|
||||
|
|
@ -93,6 +95,7 @@ internal class TangemPayCardLimitSetupModelTest {
|
|||
paymentAccountStatusSupplier = paymentAccountStatusSupplier,
|
||||
setTangemPayCardLimitUseCase = setLimitUseCase,
|
||||
uiMessageSender = uiMessageSender,
|
||||
analytics = analytics,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +125,29 @@ internal class TangemPayCardLimitSetupModelTest {
|
|||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Analytics {
|
||||
@Test
|
||||
fun `GIVEN model WHEN init THEN LimitManagementOpened is sent`() {
|
||||
val model = createModel()
|
||||
|
||||
verify(exactly = 1) { analytics.send(TangemPayAnalyticsEvents.LimitManagementOpened()) }
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN model WHEN onSubmitClick THEN LimitChangeConfirmed is sent`() {
|
||||
val model = createModel()
|
||||
|
||||
model.uiState.value.amountFieldModel.onValueChange("100")
|
||||
model.uiState.value.onSubmitClick()
|
||||
verify(exactly = 1) { analytics.send(TangemPayAnalyticsEvents.LimitChangeConfirmed("100")) }
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Presets {
|
||||
|
||||
@Test
|
||||
|
|
@ -146,14 +171,17 @@ internal class TangemPayCardLimitSetupModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun provideTestCases() = listOf(
|
||||
Arguments.of("0", false),
|
||||
Arguments.of("0.99", false),
|
||||
Arguments.of("1", true),
|
||||
Arguments.of("100", true),
|
||||
Arguments.of("-1", false),
|
||||
Arguments.of("", false),
|
||||
Arguments.of("abc", true),
|
||||
Arguments.of("1001", false),
|
||||
)
|
||||
private companion object {
|
||||
@JvmStatic
|
||||
fun provideTestCases() = listOf(
|
||||
Arguments.of("0", false),
|
||||
Arguments.of("0.99", false),
|
||||
Arguments.of("1", true),
|
||||
Arguments.of("100", true),
|
||||
Arguments.of("-1", false),
|
||||
Arguments.of("", false),
|
||||
Arguments.of("abc", true),
|
||||
Arguments.of("1001", false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -354,7 +354,7 @@ internal enum class Wallet2CobrandImage(
|
|||
WinterSakura(
|
||||
cards2ResId = R.drawable.ill_winter_sakura_card2_120_106,
|
||||
cards3ResId = R.drawable.ill_winter_sakura_card3_120_106,
|
||||
batchIds = setOf("AF990053", "AF990054", "AF990055"),
|
||||
batchIds = setOf("AF990053", "AF990054", "AF990055", "AF990074", "AF990075", "AF990076"),
|
||||
),
|
||||
|
||||
LockedMoney(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue