Updated on 2026-08-14
This commit is contained in:
commit
94f1dfa29e
63 changed files with 878 additions and 555 deletions
|
|
@ -87,7 +87,7 @@ internal class DeepLinkFactory @Inject constructor(
|
|||
fun checkRoutingReadiness(appRoute: AppRoute) {
|
||||
permittedAppRoute.value = when (appRoute) {
|
||||
AppRoute.Initial,
|
||||
AppRoute.Home,
|
||||
is AppRoute.Home,
|
||||
is AppRoute.Welcome,
|
||||
is AppRoute.PushNotification,
|
||||
is AppRoute.Disclaimer,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.ui.extensions.combinedReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -97,7 +98,9 @@ class AmountStateConverter(
|
|||
* @property maxEnterAmount max enter amount data
|
||||
* @property cryptoCurrencyStatus current cryptocurrency status
|
||||
* @property iconStateConverter currency icon converter
|
||||
* @property isBalanceHidden is balance hidden status
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class AmountStateConverterV2(
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val appCurrency: AppCurrency,
|
||||
|
|
@ -105,6 +108,7 @@ class AmountStateConverterV2(
|
|||
private val maxEnterAmount: EnterAmountBoundary,
|
||||
private val iconStateConverter: CryptoCurrencyToIconStateConverter,
|
||||
private val isRedesignEnabled: Boolean,
|
||||
private val isBalanceHidden: Boolean,
|
||||
) : Converter<AmountParameters, AmountState> {
|
||||
|
||||
private val amountFieldConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -120,6 +124,12 @@ class AmountStateConverterV2(
|
|||
val crypto = maxEnterAmount.amount.format { crypto(cryptoCurrencyStatus.currency) }
|
||||
val noFeeRate = cryptoCurrencyStatus.value.fiatRate.isNullOrZero()
|
||||
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||
return AmountState.Empty(
|
||||
isRedesignEnabled = isRedesignEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
return AmountState.Data(
|
||||
title = value.title,
|
||||
availableBalance = if (isRedesignEnabled) {
|
||||
|
|
@ -127,11 +137,12 @@ class AmountStateConverterV2(
|
|||
stringReference(crypto),
|
||||
stringReference(" $DOT "),
|
||||
stringReference(fiat),
|
||||
)
|
||||
).orMaskWithStars(isBalanceHidden)
|
||||
} else {
|
||||
resourceReference(R.string.common_crypto_fiat_format, wrappedList(crypto, fiat))
|
||||
.orMaskWithStars(isBalanceHidden)
|
||||
},
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
availableBalanceShort = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||
tokenName = stringReference(cryptoCurrencyStatus.currency.name),
|
||||
tokenIconState = iconStateConverter.convert(cryptoCurrencyStatus.currency),
|
||||
amountTextField = amountFieldConverter.convert(value.value),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.combinedReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -28,6 +29,7 @@ class AmountBoundaryUpdateTransformer(
|
|||
private val maxEnterAmount: EnterAmountBoundary,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isRedesignEnabled: Boolean,
|
||||
private val isBalanceHidden: Boolean,
|
||||
) : Transformer<AmountState> {
|
||||
|
||||
override fun transform(prevState: AmountState): AmountState {
|
||||
|
|
@ -47,8 +49,8 @@ class AmountBoundaryUpdateTransformer(
|
|||
}
|
||||
|
||||
return prevState.copy(
|
||||
availableBalance = availableBalance,
|
||||
availableBalanceShort = stringReference(crypto),
|
||||
availableBalance = availableBalance.orMaskWithStars(isBalanceHidden),
|
||||
availableBalanceShort = stringReference(crypto).orMaskWithStars(isBalanceHidden),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ class AmountFieldChangeTransformer(
|
|||
|
||||
val amountTextField = prevState.amountTextField
|
||||
|
||||
if (value.isEmpty()) return prevState.emptyState()
|
||||
if (value.isEmpty()) return prevState.emptyState(maxEnterAmount.fiatRate)
|
||||
val cryptoDecimals = amountTextField.cryptoAmount.decimals
|
||||
val fiatDecimals = amountTextField.fiatAmount.decimals
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ class AmountFieldChangeTransformer(
|
|||
error = when {
|
||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||
isLessThanMinimumIfProvided -> {
|
||||
val minimumAmount = minimumTransactionAmount?.amount.format {
|
||||
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ class AmountFieldChangeTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun AmountState.Data.emptyState(): AmountState.Data {
|
||||
private fun AmountState.Data.emptyState(fiatRate: BigDecimal?): AmountState.Data {
|
||||
return copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
reduceAmountBy = BigDecimal.ZERO,
|
||||
|
|
@ -104,7 +104,7 @@ class AmountFieldChangeTransformer(
|
|||
value = "",
|
||||
fiatValue = "",
|
||||
cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO),
|
||||
fiatAmount = amountTextField.fiatAmount.copy(value = BigDecimal.ZERO),
|
||||
fiatAmount = amountTextField.fiatAmount.copy(value = if (fiatRate != null) BigDecimal.ZERO else null),
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.None,
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ class AmountFieldConverterV2(
|
|||
val cryptoAmount = cryptoDecimal.convertToAmount(cryptoCurrencyStatus.currency)
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val (fiatValue, fiatDecimal) = when {
|
||||
value.isEmpty() -> "" to BigDecimal.ZERO
|
||||
fiatRate.isNullOrZero() -> "" to null
|
||||
value.isEmpty() -> "" to BigDecimal.ZERO
|
||||
else -> {
|
||||
val fiatDecimal = fiatRate?.multiply(cryptoDecimal)
|
||||
val fiatValue = fiatDecimal?.parseBigDecimal(FIAT_DECIMALS).orEmpty()
|
||||
|
|
|
|||
|
|
@ -93,6 +93,13 @@ object AmountStatePreviewData {
|
|||
amountTextField = amountWithValueState.amountTextField.copy(isFiatValue = false),
|
||||
)
|
||||
|
||||
val amountStateV2WithoutRates = amountState.copy(
|
||||
amountTextField = amountState.amountTextField.copy(
|
||||
fiatAmount = amountState.amountTextField.fiatAmount.copy(
|
||||
value = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
val amountErrorState = amountWithValueState.copy(
|
||||
amountTextField = amountWithValueState.amountTextField.copy(
|
||||
isError = true,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Alignment.Companion.BottomCenter
|
||||
import androidx.compose.ui.Alignment.Companion.TopCenter
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
|
|
@ -141,7 +142,6 @@ private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -
|
|||
AmountFieldCurrencyInfo(
|
||||
amountUM = amountUM,
|
||||
onCurrencyChange = onCurrencyChange,
|
||||
|
||||
)
|
||||
AmountFieldError(
|
||||
isError = amountUM.amountTextField.isError,
|
||||
|
|
@ -157,6 +157,8 @@ private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -
|
|||
|
||||
@Composable
|
||||
private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
|
||||
val isFiatAvailable = amountUM.amountTextField.fiatAmount.value != null
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
|
|
@ -168,6 +170,7 @@ private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurre
|
|||
indication = null,
|
||||
onClick = { onCurrencyChange(!amountUM.amountTextField.isFiatValue) },
|
||||
)
|
||||
.alpha(if (isFiatAvailable) 1f else 0f)
|
||||
.padding(4.dp),
|
||||
) {
|
||||
val iconRotateState by animateFloatAsState(
|
||||
|
|
@ -327,6 +330,7 @@ private class AmountFieldV2PreviewProvider : PreviewParameterProvider<AmountStat
|
|||
AmountStatePreviewData.emptyState,
|
||||
AmountStatePreviewData.amountState,
|
||||
AmountStatePreviewData.amountErrorState,
|
||||
AmountStatePreviewData.amountStateV2WithoutRates,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -304,6 +304,8 @@
|
|||
<string name="details_title">Подробности</string>
|
||||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="disclaimer_title">Условия использования</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy адрес</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Отправка средств в другой сети может повлечь потерю средств.</string>
|
||||
<string name="email_preface_wc_error">Привет, команда поддержки, у меня возникла ошибка с кодом: %s</string>
|
||||
<string name="email_subject_wc_error">Ошибка WalletConnect</string>
|
||||
<string name="error_wrong_wallet_tapped">Вы использовали карту или кольцо от другого кошелька. Приложите карту или кольцо, связанную с этим кошельком.</string>
|
||||
|
|
@ -858,7 +860,7 @@
|
|||
<string name="send_gas_limit_footer">Это максимальное количество газа, которое будет потрачено на выполнение транзакции или контракта. Лимит газа предотвращает неожиданные или неограниченные расходы при выполнении транзакции.</string>
|
||||
<string name="send_gas_price">Цена газа</string>
|
||||
<string name="send_gas_price_footer">Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана.</string>
|
||||
<string name="send_max_amount">Всё</string>
|
||||
<string name="send_max_amount">Макс</string>
|
||||
<string name="send_max_amount_label">Максимальная сумма</string>
|
||||
<string name="send_max_fee">Комиссия не превысит</string>
|
||||
<string name="send_memo_destination_tag_error">Недопустимый Memo</string>
|
||||
|
|
@ -1081,6 +1083,7 @@
|
|||
<string name="swap_story_second_title">Лучшие курсы</string>
|
||||
<string name="swap_story_third_subtitle">Интуитивный обмен в пару касаний — без сложностей и ожидания</string>
|
||||
<string name="swap_story_third_title">Проще простого</string>
|
||||
<string name="swap_via_provider">Обмен через провайдера</string>
|
||||
<string name="swapping_alert_cex_description">В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя.</string>
|
||||
<string name="swapping_alert_cex_description_with_slippage">В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %1$s от биржи обратно на адрес пользователя \n\nПроскальзывание провайдера составляет до %2$s</string>
|
||||
<string name="swapping_alert_dex_description">В сумму включена комиссия провайдера сервиса.</string>
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@
|
|||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="disclaimer_title">Terms of service</string>
|
||||
<string name="domain_receive_assets_default_address">Default Address</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy Address</string>
|
||||
<string name="domain_receive_assets_navigation_title">Receive assets</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Sending assets in other networks will result in permanent loss.</string>
|
||||
<string name="domain_receive_assets_onboarding_network_name">%s network</string>
|
||||
|
|
@ -985,6 +986,7 @@
|
|||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_unreachable_error_text">Check your network connection</string>
|
||||
<string name="send_fee_unreachable_error_title">Network fee info unreachable</string>
|
||||
<string name="send_from_title">You send</string>
|
||||
<string name="send_from_wallet_android">From</string>
|
||||
<string name="send_from_wallet_name">From %s</string>
|
||||
<string name="send_gas_limit">Gas limit</string>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -30,6 +31,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
* @param shape icon button shape
|
||||
* @param background background color
|
||||
* @param iconTint icon color
|
||||
* @param innerPadding icon padding inside background area
|
||||
*
|
||||
* [Show in Figma](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=4105-1439&t=nnYBX1qCZmUNhBDf-4)
|
||||
*/
|
||||
|
|
@ -41,6 +43,7 @@ fun TangemIconButton(
|
|||
shape: RoundedCornerShape = RoundedCornerShape(24.dp),
|
||||
background: Color = TangemTheme.colors.button.secondary,
|
||||
iconTint: Color = TangemTheme.colors.icon.secondary,
|
||||
innerPadding: Dp = 4.dp,
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(ImageVector.vectorResource(iconRes)),
|
||||
|
|
@ -50,7 +53,7 @@ fun TangemIconButton(
|
|||
.size(24.dp)
|
||||
.clip(shape)
|
||||
.background(background)
|
||||
.padding(4.dp)
|
||||
.padding(innerPadding)
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -240,15 +240,19 @@ internal class DefaultTransactionRepository(
|
|||
val validator = walletManager as? TransactionValidator
|
||||
|
||||
if (validator != null) {
|
||||
val transactionData = walletManager.createTransaction(
|
||||
amount = amount,
|
||||
fee = fee ?: Fee.Common(amount = amount),
|
||||
destination = destination,
|
||||
).copy(
|
||||
extras = getMemoExtras(networkId = network.rawId, memo = memo),
|
||||
)
|
||||
try {
|
||||
val transactionData = walletManager.createTransaction(
|
||||
amount = amount,
|
||||
fee = fee ?: Fee.Common(amount = amount),
|
||||
destination = destination,
|
||||
).copy(
|
||||
extras = getMemoExtras(networkId = network.rawId, memo = memo),
|
||||
)
|
||||
|
||||
validator.validate(transactionData = transactionData)
|
||||
validator.validate(transactionData = transactionData)
|
||||
} catch (ex: Exception) {
|
||||
Result.failure(ex)
|
||||
}
|
||||
} else {
|
||||
Timber.e("${walletManager?.wallet?.blockchain} does not support transaction validation")
|
||||
Result.success(Unit)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class DefaultWalletAddressServiceRepository(
|
|||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
walletManager?.wallet?.ens
|
||||
walletManager?.wallet?.ens.takeIf { it.isNullOrEmpty().not() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ class DefaultWalletAddressServiceRepository(
|
|||
)
|
||||
|
||||
if (walletManager is NameResolver) {
|
||||
walletManager.reverseResolve(address.toByteArray())
|
||||
walletManager.reverseResolve(address)
|
||||
} else {
|
||||
ReverseResolveAddressResult.NotSupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,9 @@ data class TokenReceiveConfig(
|
|||
data class ReceiveAddressModel(
|
||||
val nameService: NameService,
|
||||
val value: String,
|
||||
val displayName: String,
|
||||
) {
|
||||
enum class NameService {
|
||||
Default, Ens
|
||||
Default, Legacy, Ens
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
||||
sealed class TokenReceiveNewAnalyticsEvent(
|
||||
|
|
@ -27,11 +28,13 @@ sealed class TokenReceiveNewAnalyticsEvent(
|
|||
class ButtonCopyAddress(
|
||||
token: String,
|
||||
blockchainName: String,
|
||||
tokenReceiveSource: TokenReceiveCopyActionSource,
|
||||
) : TokenReceiveNewAnalyticsEvent(
|
||||
event = "Button - Copy Address",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchainName,
|
||||
SOURCE to tokenReceiveSource.name,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -56,4 +59,8 @@ sealed class TokenReceiveNewAnalyticsEvent(
|
|||
BLOCKCHAIN to blockchainName,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
enum class TokenReceiveCopyActionSource {
|
||||
Main, Token, Receive, QR
|
||||
}
|
||||
|
|
@ -35,7 +35,13 @@ class GetReverseResolvedEnsAddressUseCase(private val walletAddressServiceReposi
|
|||
return when (reverseResolveAddressResult) {
|
||||
is ReverseResolveAddressResult.Error -> EnsAddress.Error(reverseResolveAddressResult.error)
|
||||
ReverseResolveAddressResult.NotSupported -> EnsAddress.NotSupported
|
||||
is ReverseResolveAddressResult.Resolved -> EnsAddress.Address(reverseResolveAddressResult.name)
|
||||
is ReverseResolveAddressResult.Resolved -> {
|
||||
if (reverseResolveAddressResult.name.isEmpty()) {
|
||||
EnsAddress.NotSupported
|
||||
} else {
|
||||
EnsAddress.Address(reverseResolveAddressResult.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,14 +21,12 @@ class ValidateTransactionUseCase(
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Either<Throwable, Unit> = Either.catch {
|
||||
transactionRepository.validateTransaction(
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
).fold(onSuccess = { Unit.right() }, onFailure = { it.left() })
|
||||
}
|
||||
): Either<Throwable, Unit> = transactionRepository.validateTransaction(
|
||||
amount = amount,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destination,
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
).fold(onSuccess = { Unit.right() }, onFailure = { it.left() })
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.card.common.util.cardTypesResolver
|
|||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
|
|
@ -75,6 +76,7 @@ internal class HomeModel @Inject constructor(
|
|||
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -206,6 +208,7 @@ internal class HomeModel @Inject constructor(
|
|||
}
|
||||
},
|
||||
ifRight = {
|
||||
reduxStateHolder.onUserWalletSelected(userWallet)
|
||||
setLoading(false)
|
||||
sendSignedInCardAnalyticsEvent(scanResponse)
|
||||
appRouter.replaceAll(AppRoute.Wallet)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.models.ReceiveAddressModel
|
|||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
|
|
@ -390,16 +391,17 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
displayName = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Default,
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
displayName = "${cryptoCurrency.name} (${cryptoCurrency.symbol})",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,16 +209,17 @@ internal class NFTReceiveModel @Inject constructor(
|
|||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
displayName = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Default,
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
displayName = cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.send.v2.api.subcomponents.amount.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TYPE
|
||||
|
||||
sealed class CommonSendAmountAnalyticEvents(
|
||||
|
|
@ -22,7 +24,16 @@ sealed class CommonSendAmountAnalyticEvents(
|
|||
/** Max amount button clicked */
|
||||
data class MaxAmountButtonClicked(
|
||||
val categoryName: String,
|
||||
) : CommonSendAmountAnalyticEvents(category = categoryName, event = "Max Amount Taped")
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
) : CommonSendAmountAnalyticEvents(
|
||||
category = categoryName,
|
||||
event = "Max Amount Taped",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
||||
enum class SelectedCurrencyType(val value: String) {
|
||||
Token("Token"),
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onBack() {
|
||||
router.pop()
|
||||
modelScope.launch {
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ internal sealed class SendAnalyticEvents(
|
|||
val feeType: AnalyticsParam.FeeType,
|
||||
val blockchain: String,
|
||||
val nonceNotEmpty: Boolean,
|
||||
private val ensStatus: AnalyticsParam.EnsStatus,
|
||||
) : SendAnalyticEvents(
|
||||
event = "Transaction Sent Screen Opened",
|
||||
params = mapOf(
|
||||
|
|
@ -32,10 +31,7 @@ internal sealed class SendAnalyticEvents(
|
|||
FEE_TYPE to feeType.value,
|
||||
BLOCKCHAIN to blockchain,
|
||||
NONCE to nonceNotEmpty.toString().capitalize(),
|
||||
ENS_ADDRESS to when (ensStatus) {
|
||||
AnalyticsParam.EnsStatus.EMPTY -> false.toString()
|
||||
AnalyticsParam.EnsStatus.FULL -> true.toString()
|
||||
},
|
||||
ENS_ADDRESS to (blockchain == "Ethereum").toString(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ internal class SendAnalyticHelper @Inject constructor(
|
|||
feeType = feeType,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
nonceNotEmpty = feeSelectorUM.nonce != null,
|
||||
ensStatus = getEnsStatus(sendUM),
|
||||
),
|
||||
)
|
||||
analyticsEventHandler.send(
|
||||
|
|
@ -53,14 +52,4 @@ internal class SendAnalyticHelper @Inject constructor(
|
|||
else -> Basic.TransactionSent.MemoType.Null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnsStatus(sendUM: SendUM): AnalyticsParam.EnsStatus {
|
||||
val blockchainAddressForEns =
|
||||
(sendUM.destinationUM as? DestinationUM.Content)?.addressTextField?.blockchainAddress
|
||||
return if (blockchainAddressForEns != null) {
|
||||
AnalyticsParam.EnsStatus.FULL
|
||||
} else {
|
||||
AnalyticsParam.EnsStatus.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,7 @@ internal class SendConfirmComponent(
|
|||
userWalletId = params.userWallet.walletId,
|
||||
cryptoCurrency = params.cryptoCurrencyStatus.currency,
|
||||
cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow,
|
||||
isBalanceHidingFlow = params.isBalanceHidingFlow,
|
||||
),
|
||||
onResult = model::onAmountResult,
|
||||
onClick = model::showEditAmount,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
|
|
@ -105,6 +106,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val feeReloadTrigger: SendFeeReloadTrigger,
|
||||
private val sendAmountReduceTrigger: SendAmountReduceTrigger,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
|
||||
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback {
|
||||
|
||||
|
|
@ -121,6 +123,9 @@ internal class SendConfirmModel @Inject constructor(
|
|||
private val _uiState = MutableStateFlow(params.state)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
val isBalanceHiddenFlow: StateFlow<Boolean>
|
||||
field = MutableStateFlow(false)
|
||||
|
||||
private val amountState
|
||||
get() = uiState.value.amountUM as? AmountState.Data
|
||||
private val destinationUM
|
||||
|
|
@ -139,8 +144,16 @@ internal class SendConfirmModel @Inject constructor(
|
|||
reduceAmountBy = amountState?.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = amountState?.isIgnoreReduce == true,
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
fee = if (uiState.value.isRedesignEnabled) {
|
||||
feeUMV2?.selectedFeeItem?.fee
|
||||
} else {
|
||||
feeSelectorUM?.selectedFee
|
||||
},
|
||||
feeError = if (uiState.value.isRedesignEnabled) {
|
||||
(uiState.value.feeSelectorUM as? FeeSelectorUMRedesigned.Error)?.error
|
||||
} else {
|
||||
(feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error
|
||||
},
|
||||
)
|
||||
|
||||
private var sendIdleTimer: Long = 0L
|
||||
|
|
@ -156,6 +169,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
subscribeOnNotificationsUpdateTrigger()
|
||||
subscribeOnCheckFeeResultUpdates()
|
||||
initialState()
|
||||
subscribeOnBalanceHidden()
|
||||
}
|
||||
|
||||
fun updateState(state: SendUM) {
|
||||
|
|
@ -511,6 +525,16 @@ internal class SendConfirmModel @Inject constructor(
|
|||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnBalanceHidden() {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach { balanceHidingSettings ->
|
||||
isBalanceHiddenFlow.update { balanceHidingSettings.isBalanceHidden }
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateConfirmNotifications() {
|
||||
modelScope.launch {
|
||||
notificationsUpdateTrigger.triggerUpdate(
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
val stackState by childStack.subscribeAsState()
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
BackHandler(onBack = ::onChildBack)
|
||||
BackHandler(onBack = model::onBackClick)
|
||||
SendContent(
|
||||
navigationUM = state.navigationUM,
|
||||
stackState = stackState,
|
||||
|
|
|
|||
|
|
@ -120,8 +120,16 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
get() = ConfirmData(
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
fee = if (uiState.value.isRedesignEnabled) {
|
||||
(uiState.value.feeSelectorUM as? FeeSelectorUMRedesigned.Content)?.selectedFeeItem?.fee
|
||||
} else {
|
||||
feeSelectorUM?.selectedFee
|
||||
},
|
||||
feeError = if (uiState.value.isRedesignEnabled) {
|
||||
(uiState.value.feeSelectorUM as? FeeSelectorUMRedesigned.Error)?.error
|
||||
} else {
|
||||
(feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error
|
||||
},
|
||||
)
|
||||
|
||||
private var sendIdleTimer: Long = 0L
|
||||
|
|
@ -255,11 +263,15 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
|
||||
private fun initialState() {
|
||||
val confirmUM = uiState.value.confirmUM
|
||||
val feeUM = uiState.value.feeUM
|
||||
val isEmptyFee = if (uiState.value.isRedesignEnabled) {
|
||||
uiState.value.feeSelectorUM !is FeeSelectorUMRedesigned.Content
|
||||
} else {
|
||||
uiState.value.feeUM is FeeUM.Empty
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
if (confirmUM is ConfirmUM.Empty || feeUM is FeeUM.Empty) {
|
||||
if (confirmUM is ConfirmUM.Empty || isEmptyFee) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
confirmUM = NFTSendConfirmInitialStateTransformer(
|
||||
|
|
@ -277,11 +289,17 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
notificationsUpdateListener.hasErrorFlow
|
||||
.onEach { hasError ->
|
||||
_uiState.update {
|
||||
val feeUM = it.feeUM as? FeeUM.Content
|
||||
val feeSelectorUM = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
val isFeeNotNull = if (uiState.value.isRedesignEnabled) {
|
||||
it.feeSelectorUM is FeeSelectorUMRedesigned.Content
|
||||
} else {
|
||||
val feeUM = it.feeUM as? FeeUM.Content
|
||||
val feeSelectorUM = feeUM?.feeSelectorUM as? FeeSelectorUM.Content
|
||||
feeSelectorUM != null
|
||||
}
|
||||
|
||||
it.copy(
|
||||
confirmUM = (it.confirmUM as? ConfirmUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = !hasError && feeSelectorUM != null,
|
||||
isPrimaryButtonEnabled = !hasError && isFeeNotNull,
|
||||
) ?: it.confirmUM,
|
||||
)
|
||||
}
|
||||
|
|
@ -290,9 +308,8 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun verifyAndSendTransaction() {
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee ?: return
|
||||
val destination = confirmData.enteredDestination ?: return
|
||||
val fee = confirmData.fee ?: return
|
||||
val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: return
|
||||
|
||||
val sdkNFTAsset = NFTSdkAssetConverter.convertBack(params.nftAsset)
|
||||
|
|
@ -302,7 +319,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
ownerAddress = ownerAddress,
|
||||
nftAsset = sdkNFTAsset.second,
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
memo = confirmData.enteredMemo,
|
||||
destinationAddress = destination,
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrency.network,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.transaction.error.GetFeeError
|
|||
import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.nft.entity.NFTSendSuccessTrigger
|
||||
import com.tangem.features.send.v2.api.NFTSendComponent
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
|
|
@ -74,6 +75,7 @@ internal class NFTSendModel @Inject constructor(
|
|||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val alertFactory: SendConfirmAlertFactory,
|
||||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val nftSendSuccessTrigger: NFTSendSuccessTrigger,
|
||||
) : Model(), SendNFTComponentCallback, NFTSendSuccessComponent.ModelCallback {
|
||||
|
||||
val params: NFTSendComponent.Params = paramsContainer.require()
|
||||
|
|
@ -119,6 +121,11 @@ internal class NFTSendModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onBackClick() {
|
||||
if (currentRouteFlow.value == ConfirmSuccess) {
|
||||
modelScope.launch {
|
||||
nftSendSuccessTrigger.triggerSuccessNFTSend()
|
||||
}
|
||||
}
|
||||
router.pop()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.navigationButtons.NavigationModelCallback
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
|
|
@ -85,7 +86,7 @@ internal class NFTSendSuccessComponent @AssistedInject constructor(
|
|||
val callback: ModelCallback,
|
||||
)
|
||||
|
||||
interface ModelCallback {
|
||||
interface ModelCallback : NavigationModelCallback {
|
||||
fun onResult(nftSendUM: NFTSendUM)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.features.send.v2.sendnft.success.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -31,7 +30,6 @@ internal class NFTSendSuccessModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
) : Model() {
|
||||
|
|
@ -64,16 +62,14 @@ internal class NFTSendSuccessModel @Inject constructor(
|
|||
isValid = true,
|
||||
),
|
||||
)
|
||||
appRouter.pop()
|
||||
params.callback.onBackClick()
|
||||
},
|
||||
primaryButton = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_close),
|
||||
iconRes = null,
|
||||
isEnabled = true,
|
||||
isHapticClick = false,
|
||||
onClick = {
|
||||
appRouter.pop()
|
||||
},
|
||||
onClick = params.callback::onBackClick,
|
||||
),
|
||||
prevButton = null,
|
||||
secondaryPairButtonsUM = NavigationButton(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal sealed class SendAmountComponentParams {
|
|||
abstract val isRedesignEnabled: Boolean
|
||||
abstract val cryptoCurrency: CryptoCurrency
|
||||
abstract val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
|
||||
abstract val isBalanceHidingFlow: StateFlow<Boolean>
|
||||
|
||||
data class AmountParams(
|
||||
override val state: AmountState,
|
||||
|
|
@ -31,9 +32,9 @@ internal sealed class SendAmountComponentParams {
|
|||
override val isRedesignEnabled: Boolean,
|
||||
override val cryptoCurrency: CryptoCurrency,
|
||||
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
val callback: ModelCallback,
|
||||
val currentRoute: StateFlow<CommonSendRoute>,
|
||||
val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
) : SendAmountComponentParams()
|
||||
|
||||
data class AmountBlockParams(
|
||||
|
|
@ -45,6 +46,7 @@ internal sealed class SendAmountComponentParams {
|
|||
override val isRedesignEnabled: Boolean,
|
||||
override val cryptoCurrency: CryptoCurrency,
|
||||
override val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>,
|
||||
override val isBalanceHidingFlow: StateFlow<Boolean>,
|
||||
val userWallet: UserWallet,
|
||||
val blockClickEnableFlow: StateFlow<Boolean>,
|
||||
) : SendAmountComponentParams()
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
|||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.send.v2.api.SendFeatureToggles
|
||||
import com.tangem.features.send.v2.api.entity.PredefinedValues
|
||||
import com.tangem.features.send.v2.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
|
||||
|
|
@ -66,6 +67,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val sendAmountAlertFactory: SendAmountAlertFactory,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
) : Model(), SendAmountClickIntents {
|
||||
|
||||
private val params: SendAmountComponentParams = paramsContainer.require()
|
||||
|
|
@ -102,6 +104,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
subscribeOnAmountReduceToTriggerUpdates()
|
||||
subscribeOnAmountIgnoreReduceTriggerUpdates()
|
||||
subscribeOnAmountUpdateTriggerUpdates()
|
||||
subscribeOnBalanceHiddenUpdates()
|
||||
}
|
||||
|
||||
private fun initAppCurrency() {
|
||||
|
|
@ -119,6 +122,20 @@ internal class SendAmountModel @Inject constructor(
|
|||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnBalanceHiddenUpdates() {
|
||||
params.isBalanceHidingFlow.onEach { isBalanceHidden ->
|
||||
_uiState.update(
|
||||
AmountBoundaryUpdateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxEnterAmount = maxAmountBoundary,
|
||||
appCurrency = appCurrency,
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnCryptoCurrencyStatusFlow() {
|
||||
params.cryptoCurrencyStatusFlow
|
||||
.onEach { newCryptoCurrencyStatus ->
|
||||
|
|
@ -150,6 +167,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
maxEnterAmount = maxAmountBoundary,
|
||||
appCurrency = appCurrency,
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -160,6 +178,7 @@ internal class SendAmountModel @Inject constructor(
|
|||
|
||||
private fun initialState() {
|
||||
if (uiState.value is AmountState.Empty && userWallet != null) {
|
||||
val isSingleWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
_uiState.update {
|
||||
AmountStateConverterV2(
|
||||
clickIntents = this,
|
||||
|
|
@ -168,12 +187,17 @@ internal class SendAmountModel @Inject constructor(
|
|||
maxEnterAmount = maxAmountBoundary,
|
||||
iconStateConverter = CryptoCurrencyToIconStateConverter(),
|
||||
isRedesignEnabled = sendFeatureToggles.isSendRedesignEnabled,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
).convert(
|
||||
AmountParameters(
|
||||
title = resourceReference(
|
||||
R.string.send_from_wallet_name,
|
||||
WrappedList(listOf(userWallet?.name.orEmpty())), // TODO [REDACTED_TASK_KEY]
|
||||
),
|
||||
title = if (isSingleWallet) {
|
||||
resourceReference(R.string.send_from_title)
|
||||
} else {
|
||||
resourceReference(
|
||||
R.string.send_from_wallet_name,
|
||||
WrappedList(listOf(userWallet?.name.orEmpty())), // TODO [REDACTED_TASK_KEY]
|
||||
)
|
||||
},
|
||||
value = "",
|
||||
),
|
||||
)
|
||||
|
|
@ -218,7 +242,11 @@ internal class SendAmountModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(categoryName = analyticsCategoryName),
|
||||
CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(
|
||||
categoryName = analyticsCategoryName,
|
||||
token = params.cryptoCurrency.symbol,
|
||||
blockchain = params.cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -257,6 +285,11 @@ internal class SendAmountModel @Inject constructor(
|
|||
private fun confirmConvertToToken() {
|
||||
val amountParams = params as? SendAmountComponentParams.AmountParams ?: return
|
||||
val amountFieldData = uiState.value as? AmountState.Data
|
||||
_uiState.update {
|
||||
(it as? AmountState.Data)?.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
) ?: it
|
||||
}
|
||||
amountParams.callback.onConvertToAnotherToken(amountFieldData?.amountTextField?.value.orEmpty())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ internal class SendDestinationModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
initSenderAddress()
|
||||
}
|
||||
initSenderAddress()
|
||||
}
|
||||
|
||||
fun updateState(destinationUM: DestinationUM) {
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ internal class NotificationsModel @Inject constructor(
|
|||
) {
|
||||
val validationError = validateTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
amount = sendingAmount.convertToSdkAmount(currency),
|
||||
amount = enteredAmount.convertToSdkAmount(currency),
|
||||
fee = fee,
|
||||
memo = memo,
|
||||
destination = destinationAddress,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.tangem.domain.swap.usecase.GetSwapQuoteUseCase
|
|||
import com.tangem.domain.swap.usecase.SelectInitialPairUseCase
|
||||
import com.tangem.domain.tokens.GetMinimumTransactionAmountSyncUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.send.v2.api.subcomponents.amount.analytics.CommonSendAmountAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.swap.v2.api.choosetoken.SwapChooseTokenNetworkListener
|
||||
|
|
@ -91,6 +92,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
|
||||
|
||||
private val params: SwapAmountComponentParams = paramsContainer.require()
|
||||
|
|
@ -105,10 +107,10 @@ internal class SwapAmountModel @Inject constructor(
|
|||
private var secondaryMaximumAmountBoundary: EnterAmountBoundary? = null
|
||||
private var secondaryMinimumAmountBoundary: EnterAmountBoundary? = null
|
||||
|
||||
var userCountry: UserCountry = UserCountry.Other(Locale.getDefault().country)
|
||||
private var userCountry: UserCountry = UserCountry.Other(Locale.getDefault().country)
|
||||
val bottomSheetNavigation: SlotNavigation<SwapChooseProviderConfig> = SlotNavigation()
|
||||
|
||||
var showBestRateAnimation: Boolean = false
|
||||
private var showBestRateAnimation: Boolean = false
|
||||
|
||||
val uiState: StateFlow<SwapAmountUM>
|
||||
field = MutableStateFlow(params.amountUM)
|
||||
|
|
@ -131,10 +133,14 @@ internal class SwapAmountModel @Inject constructor(
|
|||
subscribeOnAmountReduceByTriggerUpdates()
|
||||
subscribeOnAmountIgnoreReduceTriggerUpdates()
|
||||
subscribeOnReloadQuotesTriggerUpdates()
|
||||
subscribeOnBalanceHiddenUpdates()
|
||||
}
|
||||
|
||||
fun onStart() {
|
||||
startLoadingQuotesTask(isSilentReload = false)
|
||||
quoteTaskScheduler.scheduleTask(
|
||||
scope = modelScope,
|
||||
task = loadQuotesTask(),
|
||||
)
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
|
|
@ -213,7 +219,11 @@ internal class SwapAmountModel @Inject constructor(
|
|||
|
||||
override fun onMaxValueClick() {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(categoryName = params.analyticsCategoryName),
|
||||
CommonSendAmountAnalyticEvents.MaxAmountButtonClicked(
|
||||
categoryName = params.analyticsCategoryName,
|
||||
token = primaryCryptoCurrency.symbol,
|
||||
blockchain = primaryCryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountValueMaxTransformer(
|
||||
|
|
@ -319,12 +329,29 @@ internal class SwapAmountModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun subscribeOnBalanceHiddenUpdates() {
|
||||
params.isBalanceHidingFlow.onEach { isHidden ->
|
||||
val isSingleWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountBalanceHiddenTransformer(
|
||||
isBalanceHidden = isHidden,
|
||||
isSingleWallet = isSingleWallet,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
swapDirection = swapDirection,
|
||||
clickIntents = this,
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun confirmSendWithSwapClose() {
|
||||
val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data
|
||||
|
||||
val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
|
||||
if (primaryCryptoCurrencyStatus != null) {
|
||||
val isSingleWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountPrimaryReadyStateTransformer(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -334,6 +361,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
clickIntents = this,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
showBestRateAnimation = showBestRateAnimation,
|
||||
isSingleWallet = isSingleWallet,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -359,6 +387,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
val isSingleWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountPrimaryReadyStateTransformer(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -368,6 +397,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
clickIntents = this,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
showBestRateAnimation = showBestRateAnimation,
|
||||
isSingleWallet = isSingleWallet,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -465,6 +495,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
val primaryStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
|
||||
if (secondaryStatus != null && primaryStatus != null) {
|
||||
initCurrencies(primaryStatus, secondaryStatus)
|
||||
val isSingleWallet = getWalletsUseCase.invokeSync().size == 1
|
||||
uiState.transformerUpdate(
|
||||
SwapAmountSecondaryReadyStateTransformer(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -476,6 +507,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
clickIntents = this@SwapAmountModel,
|
||||
isBalanceHidden = params.isBalanceHidingFlow.value,
|
||||
showBestRateAnimation = showBestRateAnimation,
|
||||
isSingleWallet = isSingleWallet,
|
||||
),
|
||||
)
|
||||
startLoadingQuotesTask(isSilentReload = false)
|
||||
|
|
@ -533,18 +565,17 @@ internal class SwapAmountModel @Inject constructor(
|
|||
} as? AmountState.Data
|
||||
|
||||
val fromAmountValue = fromAmount?.amountTextField?.cryptoAmount?.value.orZero()
|
||||
|
||||
if (fromAmount?.amountTextField?.isError == true || fromAmountValue.isNullOrZero()) {
|
||||
val isAmountScreen = params is SwapAmountComponentParams.AmountParams
|
||||
val isAmountError = fromAmount?.amountTextField?.isError == true || fromAmountValue.isNullOrZero()
|
||||
if (isAmountScreen && isAmountError) {
|
||||
uiState.transformerUpdate(SwapQuoteEmptyStateTransformer)
|
||||
return
|
||||
}
|
||||
|
||||
val swapGroups = state.swapCurrencies.getGroupWithDirection(state.swapDirection)
|
||||
|
||||
uiState.transformerUpdate(SwapQuoteLoadingStateTransformer)
|
||||
if (!isSilentReload) { uiState.transformerUpdate(SwapQuoteLoadingStateTransformer) }
|
||||
|
||||
modelScope.launch {
|
||||
val quotes = swapGroups.available.filter {
|
||||
val quotes = state.swapCurrencies.getGroupWithDirection(state.swapDirection).available.filter {
|
||||
it.currencyStatus.currency.id == toCryptoCurrency.id
|
||||
}.flatMap {
|
||||
it.providers
|
||||
|
|
@ -591,7 +622,6 @@ internal class SwapAmountModel @Inject constructor(
|
|||
needApplyFcaRestrictions = userCountry.needApplyFCARestrictions(),
|
||||
),
|
||||
)
|
||||
|
||||
feeSelectorReloadTrigger.triggerUpdate()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ internal class SwapAmountFieldConverter(
|
|||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
private val isSingleWallet: Boolean,
|
||||
) {
|
||||
|
||||
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
|
@ -48,13 +49,18 @@ internal class SwapAmountFieldConverter(
|
|||
maxEnterAmount = maxEnterAmountConverter.convert(cryptoCurrencyStatus),
|
||||
iconStateConverter = iconStateConverter,
|
||||
isRedesignEnabled = true,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
).convert(
|
||||
AmountParameters(
|
||||
title = combinedReference(
|
||||
resourceReference(R.string.send_from_wallet_android),
|
||||
stringReference(" "),
|
||||
stringReference(userWallet.name),
|
||||
),
|
||||
title = if (isSingleWallet) {
|
||||
resourceReference(R.string.send_from_title)
|
||||
} else {
|
||||
combinedReference(
|
||||
resourceReference(R.string.send_from_wallet_android),
|
||||
stringReference(" "),
|
||||
stringReference(userWallet.name),
|
||||
)
|
||||
},
|
||||
value = "",
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.model.transformers
|
||||
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountType
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountFieldConverter
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SwapAmountBalanceHiddenTransformer(
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val isSingleWallet: Boolean,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val swapDirection: SwapDirection,
|
||||
private val clickIntents: AmountScreenClickIntents,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
val content = prevState as? SwapAmountUM.Content ?: return prevState
|
||||
|
||||
val amountFieldConverter = SwapAmountFieldConverter(
|
||||
swapDirection = swapDirection,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
isSingleWallet = isSingleWallet,
|
||||
)
|
||||
|
||||
val recalculatedPrimary = amountFieldConverter.convert(
|
||||
selectedType = SwapAmountType.From,
|
||||
cryptoCurrencyStatus = content.primaryCryptoCurrencyStatus,
|
||||
) as SwapAmountFieldUM.Content
|
||||
|
||||
val oldPrimary = content.primaryAmount as? SwapAmountFieldUM.Content
|
||||
|
||||
val mergedAmountField = if (
|
||||
oldPrimary?.amountField is AmountState.Data && recalculatedPrimary.amountField is AmountState.Data
|
||||
) {
|
||||
val oldData = oldPrimary.amountField
|
||||
val newData = recalculatedPrimary.amountField
|
||||
newData.copy(
|
||||
amountTextField = oldData.amountTextField,
|
||||
selectedButton = oldData.selectedButton,
|
||||
isPrimaryButtonEnabled = oldData.isPrimaryButtonEnabled,
|
||||
isSegmentedButtonsEnabled = oldData.isSegmentedButtonsEnabled,
|
||||
isEditingDisabled = oldData.isEditingDisabled,
|
||||
reduceAmountBy = oldData.reduceAmountBy,
|
||||
isIgnoreReduce = oldData.isIgnoreReduce,
|
||||
)
|
||||
} else {
|
||||
recalculatedPrimary.amountField
|
||||
}
|
||||
|
||||
val updatedPrimaryAmount = recalculatedPrimary.copy(
|
||||
amountField = mergedAmountField,
|
||||
)
|
||||
|
||||
return content.copy(primaryAmount = updatedPrimaryAmount)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ internal class SwapAmountPrimaryReadyStateTransformer(
|
|||
private val swapDirection: SwapDirection,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val showBestRateAnimation: Boolean,
|
||||
private val isSingleWallet: Boolean,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
|
||||
private val amountFieldConverter = SwapAmountFieldConverter(
|
||||
|
|
@ -32,6 +33,7 @@ internal class SwapAmountPrimaryReadyStateTransformer(
|
|||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
isSingleWallet = isSingleWallet,
|
||||
)
|
||||
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ internal class SwapAmountSecondaryReadyStateTransformer(
|
|||
private val swapDirection: SwapDirection,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val showBestRateAnimation: Boolean,
|
||||
private val isSingleWallet: Boolean,
|
||||
) : Transformer<SwapAmountUM> {
|
||||
|
||||
private val amountFieldConverter = SwapAmountFieldConverter(
|
||||
|
|
@ -33,6 +34,7 @@ internal class SwapAmountSecondaryReadyStateTransformer(
|
|||
userWallet = userWallet,
|
||||
appCurrency = appCurrency,
|
||||
clickIntents = clickIntents,
|
||||
isSingleWallet = isSingleWallet,
|
||||
)
|
||||
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ internal class SwapAmountSetQuotesTransformer(
|
|||
|
||||
val sortedQuotes = quotes.sortedWith(SwapQuotesComparator)
|
||||
val bestQuote = findBestQuote(quotes) ?: SwapQuoteUM.Empty
|
||||
val quotesWithDiff = getQuotesWithDiff(sortedQuotes, bestQuote, isSingleProvider)
|
||||
val selectedQuote = if (isSilentReload && prevState.selectedQuote !is SwapQuoteUM.Loading) {
|
||||
prevState.selectedQuote
|
||||
quotesWithDiff.firstOrNull { it.provider?.providerId == prevState.selectedQuote.provider?.providerId }
|
||||
?: prevState.selectedQuote
|
||||
} else {
|
||||
(bestQuote as? SwapQuoteUM.Content)?.copy(
|
||||
diffPercent = DifferencePercent.Best,
|
||||
|
|
@ -55,7 +57,7 @@ internal class SwapAmountSetQuotesTransformer(
|
|||
if (updatedState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return updatedState.copy(
|
||||
isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled && quotes.isNotEmpty(),
|
||||
isPrimaryButtonEnabled = updatedState.isPrimaryButtonEnabled && quotesWithDiff.isNotEmpty(),
|
||||
swapQuotes = getQuotesWithDiff(sortedQuotes, bestQuote, isSingleProvider),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ internal class SwapAmountValueChangeTransformer(
|
|||
)
|
||||
|
||||
return (updatedState as? SwapAmountUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
selectedQuote = if (updatedState.isPrimaryButtonEnabled) {
|
||||
SwapQuoteUM.Empty
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -16,31 +16,34 @@ internal class SwapAmountValueMaxTransformer(
|
|||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
if (prevState !is SwapAmountUM.Content) return prevState
|
||||
|
||||
return prevState
|
||||
.copy(selectedQuote = SwapQuoteUM.Loading)
|
||||
.updateAmount(
|
||||
onPrimaryAmount = { primaryStatus ->
|
||||
val updatedState = prevState.updateAmount(
|
||||
onPrimaryAmount = { primaryStatus ->
|
||||
copy(
|
||||
amountField = AmountFieldSetMaxAmountTransformer(
|
||||
cryptoCurrencyStatus = primaryStatus,
|
||||
maxAmount = primaryMaximumAmountBoundary,
|
||||
minAmount = primaryMinimumAmountBoundary,
|
||||
).transform(prevState.primaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = { secondaryStatus ->
|
||||
if (secondaryMaximumAmountBoundary != null) {
|
||||
copy(
|
||||
amountField = AmountFieldSetMaxAmountTransformer(
|
||||
cryptoCurrencyStatus = primaryStatus,
|
||||
maxAmount = primaryMaximumAmountBoundary,
|
||||
minAmount = primaryMinimumAmountBoundary,
|
||||
).transform(prevState.primaryAmount.amountField),
|
||||
cryptoCurrencyStatus = secondaryStatus,
|
||||
maxAmount = secondaryMaximumAmountBoundary,
|
||||
minAmount = secondaryMinimumAmountBoundary,
|
||||
).transform(prevState.secondaryAmount.amountField),
|
||||
)
|
||||
},
|
||||
onSecondaryAmount = { secondaryStatus ->
|
||||
if (secondaryMaximumAmountBoundary != null) {
|
||||
copy(
|
||||
amountField = AmountFieldSetMaxAmountTransformer(
|
||||
cryptoCurrencyStatus = secondaryStatus,
|
||||
maxAmount = secondaryMaximumAmountBoundary,
|
||||
minAmount = secondaryMinimumAmountBoundary,
|
||||
).transform(prevState.secondaryAmount.amountField),
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return (updatedState as? SwapAmountUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = false,
|
||||
selectedQuote = SwapQuoteUM.Loading,
|
||||
) ?: updatedState
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ internal class SwapProviderStateConverter(
|
|||
|
||||
val additionalBadge = when {
|
||||
needApplyFCARestrictions && provider.isRestrictedByFCA() -> AdditionalBadge.FCAWarningList
|
||||
isNeedBestRateBadge && isBestRate && !needApplyFCARestrictions -> AdditionalBadge.BestTrade
|
||||
isNeedBestRateBadge && isBestRate -> AdditionalBadge.BestTrade
|
||||
else -> AdditionalBadge.Empty
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,13 +94,15 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
)
|
||||
activeComponent.updateState(model.uiState.value.destinationUM)
|
||||
}
|
||||
is SendWithSwapConfirmComponent -> if (model.currentRoute.value.isEditMode) {
|
||||
is SendWithSwapConfirmComponent -> {
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ConfirmationScreenOpened(
|
||||
categoryName = model.analyticCategoryName,
|
||||
),
|
||||
)
|
||||
activeComponent.updateState(model.uiState.value)
|
||||
if (model.currentRoute.value.isEditMode) {
|
||||
activeComponent.updateState(model.uiState.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
model.currentRoute.emit(stack.active.configuration)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.swap.v2.impl.sendviaswap.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN
|
||||
|
|
@ -24,7 +25,7 @@ internal sealed class SendWithSwapAnalyticEvents(
|
|||
event = "Send With Swap In Progress Screen Opened",
|
||||
params = mapOf(
|
||||
PROVIDER to providerName,
|
||||
"Commission" to if (feeType is AnalyticsParam.FeeType.Normal) "Market" else "Fast",
|
||||
FEE_TYPE to if (feeType is AnalyticsParam.FeeType.Normal) "Market" else "Fast",
|
||||
SEND_TOKEN to fromToken.symbol,
|
||||
RECEIVE_TOKEN to toToken.symbol,
|
||||
SEND_BLOCKCHAIN to fromToken.network.name,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
|
|||
is TokenReceiveRoutes.QrCode -> TokenReceiveQrCodeComponent(
|
||||
appComponentContext = appComponentContext,
|
||||
params = TokenReceiveQrCodeComponent.TokenReceiveQrCodeParams(
|
||||
network = model.params.config.cryptoCurrency.network.name,
|
||||
cryptoCurrency = model.params.config.cryptoCurrency,
|
||||
address = model.state.value.addresses[config.addressId] ?: error("Address has to be there"),
|
||||
callback = model,
|
||||
onDismiss = ::dismiss,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.ui.notifications.NotificationUM
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel
|
||||
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent
|
||||
|
|
@ -29,7 +30,7 @@ internal class TokenReceiveAssetsComponent(
|
|||
|
||||
internal interface TokenReceiveAssetsModelCallback {
|
||||
fun onQrCodeClick(id: Int)
|
||||
fun onCopyClick(id: Int)
|
||||
fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource)
|
||||
}
|
||||
|
||||
data class TokenReceiveAssetsParams(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel
|
||||
import com.tangem.features.tokenreceive.ui.TokenReceiveQrCodeContent
|
||||
|
|
@ -25,13 +27,13 @@ internal class TokenReceiveQrCodeComponent(
|
|||
}
|
||||
|
||||
internal interface TokenReceiveQrCodeModelCallback {
|
||||
fun onCopyClick(id: Int)
|
||||
fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource)
|
||||
fun onShareClick(address: String)
|
||||
}
|
||||
|
||||
data class TokenReceiveQrCodeParams(
|
||||
val id: Int,
|
||||
val network: String,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val address: ReceiveAddress,
|
||||
val callback: TokenReceiveQrCodeModelCallback,
|
||||
val onDismiss: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
package com.tangem.features.tokenreceive.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal data class ReceiveAddress(
|
||||
val value: String,
|
||||
val type: Type,
|
||||
) {
|
||||
|
||||
@Immutable
|
||||
sealed interface Type {
|
||||
data object Ens : Type
|
||||
|
||||
data class Default(
|
||||
val displayName: TextReference,
|
||||
) : Type
|
||||
sealed interface Primary : Type {
|
||||
|
||||
val displayName: TextReference
|
||||
|
||||
data class Default(override val displayName: TextReference) : Primary
|
||||
|
||||
data class Legacy(override val displayName: TextReference) : Primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
||||
import com.tangem.features.tokenreceive.component.TokenReceiveAssetsComponent
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
|
|
@ -38,13 +39,17 @@ internal class TokenReceiveAssetsModel @Inject constructor(
|
|||
internal val state: StateFlow<ReceiveAssetsUM>
|
||||
field = MutableStateFlow<ReceiveAssetsUM>(
|
||||
ReceiveAssetsUM(
|
||||
onCopyClick = params.callback::onCopyClick,
|
||||
onCopyClick = {
|
||||
params.callback.onCopyClick(
|
||||
id = it,
|
||||
source = TokenReceiveCopyActionSource.Receive,
|
||||
)
|
||||
},
|
||||
onOpenQrCodeClick = params.callback::onQrCodeClick,
|
||||
addresses = params.addresses,
|
||||
showMemoDisclaimer = params.showMemoDisclaimer,
|
||||
isEnsResultLoading = false,
|
||||
notificationConfigs = params.notificationConfigs,
|
||||
fullName = params.fullName,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,19 +12,22 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.models.Asset
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.ens.EnsAddress
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
||||
import com.tangem.domain.transaction.usecase.GetReverseResolvedEnsAddressUseCase
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
import com.tangem.features.tokenreceive.component.TokenReceiveModelCallback
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Primary
|
||||
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
|
||||
import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -74,9 +77,9 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(addressId = id))
|
||||
}
|
||||
|
||||
override fun onCopyClick(id: Int) {
|
||||
override fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource) {
|
||||
val addressToCopy = state.value.addresses[id] ?: return
|
||||
sendCopyActionAnalytic(addressToCopy)
|
||||
sendCopyActionAnalytic(addressToCopy, source)
|
||||
clipboardManager.setText(text = addressToCopy.value, isSensitive = true)
|
||||
}
|
||||
|
||||
|
|
@ -103,17 +106,35 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun mapAddresses(addresses: List<ReceiveAddressModel>): ImmutableMap<Int, ReceiveAddress> {
|
||||
private fun mapAddresses(
|
||||
addresses: List<ReceiveAddressModel>,
|
||||
networkName: String,
|
||||
): ImmutableMap<Int, ReceiveAddress> {
|
||||
val needUseToLegacyAndDefaultName = addresses.any { it.nameService == ReceiveAddressModel.NameService.Legacy }
|
||||
return buildMap {
|
||||
addresses.mapIndexed { index, model ->
|
||||
val type = when (model.nameService) {
|
||||
ReceiveAddressModel.NameService.Default -> {
|
||||
ReceiveAddress.Type.Default(
|
||||
displayName = stringReference(model.displayName),
|
||||
Primary.Default(
|
||||
displayName = if (needUseToLegacyAndDefaultName) {
|
||||
TextReference.Res(R.string.domain_receive_assets_default_address)
|
||||
} else {
|
||||
TextReference.Combined(
|
||||
wrappedList(
|
||||
TextReference.Str(networkName),
|
||||
TextReference.Str(" "),
|
||||
TextReference.Res(R.string.common_address),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
ReceiveAddressModel.NameService.Ens -> ReceiveAddress.Type.Ens
|
||||
ReceiveAddressModel.NameService.Ens -> Ens
|
||||
ReceiveAddressModel.NameService.Legacy -> Primary.Legacy(
|
||||
displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address),
|
||||
)
|
||||
}
|
||||
|
||||
put(
|
||||
key = index,
|
||||
value = ReceiveAddress(
|
||||
|
|
@ -146,10 +167,18 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
val newEnsAddresses = reverseResolveResult
|
||||
.filterIsInstance<EnsAddress.Address>()
|
||||
.filterNot { it.name in currentAddressValues }
|
||||
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = ReceiveAddress.Type.Ens) }
|
||||
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) }
|
||||
|
||||
val combinedAddresses = (state.value.addresses.values + newEnsAddresses)
|
||||
.sortedWith(compareByDescending { it.type is ReceiveAddress.Type.Ens })
|
||||
.sortedWith(
|
||||
compareBy { address ->
|
||||
when (address.type) {
|
||||
is Ens -> 0
|
||||
is Primary.Default -> 1
|
||||
is Primary.Legacy -> 2
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val updatedAddresses = combinedAddresses
|
||||
.mapIndexed { index, address -> index to address }
|
||||
|
|
@ -191,7 +220,10 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
|
||||
private fun getInitState(): TokenReceiveUM {
|
||||
return TokenReceiveUM(
|
||||
addresses = mapAddresses(params.config.receiveAddress),
|
||||
addresses = mapAddresses(
|
||||
addresses = params.config.receiveAddress,
|
||||
networkName = params.config.cryptoCurrency.network.name,
|
||||
),
|
||||
iconState = iconStateConverter.convert(params.config.cryptoCurrency),
|
||||
network = params.config.cryptoCurrency.network.name,
|
||||
isEnsResultLoading = false,
|
||||
|
|
@ -199,15 +231,16 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress) {
|
||||
private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress, source: TokenReceiveCopyActionSource) {
|
||||
val event = when (receiveAddress.type) {
|
||||
is ReceiveAddress.Type.Default -> {
|
||||
is Primary -> {
|
||||
TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = getTokenName(),
|
||||
blockchainName = params.config.cryptoCurrency.network.name,
|
||||
tokenReceiveSource = source,
|
||||
)
|
||||
}
|
||||
ReceiveAddress.Type.Ens -> {
|
||||
Ens -> {
|
||||
TokenReceiveNewAnalyticsEvent.ButtonCopyEns(
|
||||
token = getTokenName(),
|
||||
blockchainName = params.config.cryptoCurrency.network.name,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.features.tokenreceive.component.TokenReceiveQrCodeComponent
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import com.tangem.features.tokenreceive.ui.state.QrCodeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -25,10 +25,15 @@ internal class TokenReceiveQrCodeModel @Inject constructor(
|
|||
internal val state: StateFlow<QrCodeUM>
|
||||
field = MutableStateFlow<QrCodeUM>(
|
||||
QrCodeUM(
|
||||
network = params.network,
|
||||
network = params.cryptoCurrency.network.name,
|
||||
addressValue = params.address.value,
|
||||
addressName = (params.address.type as? ReceiveAddress.Type.Default)?.displayName ?: TextReference.EMPTY,
|
||||
onCopyClick = { params.callback.onCopyClick(params.id) },
|
||||
addressName = TextReference.Str("${params.cryptoCurrency.name} (${params.cryptoCurrency.symbol})"),
|
||||
onCopyClick = {
|
||||
params.callback.onCopyClick(
|
||||
id = params.id,
|
||||
source = TokenReceiveCopyActionSource.QR,
|
||||
)
|
||||
},
|
||||
onShareClick = params.callback::onShareClick,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.compose.runtime.Stable
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.iconResId
|
||||
import com.tangem.features.tokenreceive.component.TokenReceiveWarningComponent
|
||||
import com.tangem.features.tokenreceive.ui.state.WarningUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -27,7 +26,6 @@ internal class TokenReceiveWarningModel @Inject constructor(
|
|||
iconState = params.iconState,
|
||||
onWarningAcknowledged = params.callback::onWarningAcknowledged,
|
||||
network = params.network.name,
|
||||
networkIcon = params.network.iconResId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
|||
import com.tangem.core.ui.components.buttons.small.TangemIconButton
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -78,7 +79,6 @@ internal fun TokenReceiveAssetsContent(assetsUM: ReceiveAssetsUM) {
|
|||
onOpenQrCodeClick = assetsUM.onOpenQrCodeClick,
|
||||
addresses = assetsUM.addresses,
|
||||
snackbarHostState = snackbarHostState,
|
||||
fullName = assetsUM.fullName,
|
||||
)
|
||||
|
||||
if (assetsUM.isEnsResultLoading) {
|
||||
|
|
@ -125,7 +125,6 @@ private fun AddressBlock(
|
|||
onCopyClick: (id: Int) -> Unit,
|
||||
addresses: ImmutableMap<Int, ReceiveAddress>,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
fullName: String,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -133,8 +132,8 @@ private fun AddressBlock(
|
|||
val resources = context.resources
|
||||
|
||||
addresses.entries.toList().fastForEach { entry ->
|
||||
when (entry.value.type) {
|
||||
is ReceiveAddress.Type.Default -> {
|
||||
when (val type = entry.value.type) {
|
||||
is ReceiveAddress.Type.Primary -> {
|
||||
key(entry.key) {
|
||||
AddressItem(
|
||||
onCopyClick = {
|
||||
|
|
@ -152,8 +151,8 @@ private fun AddressBlock(
|
|||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onOpenQrCodeClick(entry.key)
|
||||
},
|
||||
fullName = fullName,
|
||||
address = entry.value.value,
|
||||
primaryType = type,
|
||||
)
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -184,9 +183,9 @@ private fun AddressBlock(
|
|||
@Composable
|
||||
private fun AddressItem(
|
||||
onOpenQrCodeClick: () -> Unit,
|
||||
fullName: String,
|
||||
onCopyClick: () -> Unit,
|
||||
address: String,
|
||||
primaryType: ReceiveAddress.Type.Primary,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
|
|
@ -195,52 +194,56 @@ private fun AddressItem(
|
|||
colors = CardDefaults.cardColors(containerColor = TangemTheme.colors.background.action),
|
||||
onClick = onOpenQrCodeClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 14.dp, horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 14.dp, horizontal = 12.dp),
|
||||
) {
|
||||
IdentIcon(
|
||||
address = address,
|
||||
modifier = Modifier
|
||||
.size(size = 36.dp)
|
||||
.clip(shape = RoundedCornerShape(18.dp)),
|
||||
)
|
||||
|
||||
SpacerW12()
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
EllipsisText(
|
||||
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, fullName),
|
||||
ellipsis = TextEllipsis.Middle,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IdentIcon(
|
||||
address = address,
|
||||
modifier = Modifier
|
||||
.size(size = 36.dp)
|
||||
.clip(shape = RoundedCornerShape(18.dp)),
|
||||
)
|
||||
|
||||
EllipsisText(
|
||||
text = address,
|
||||
ellipsis = TextEllipsis.Middle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
SpacerW12()
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
EllipsisText(
|
||||
text = primaryType.displayName.resolveReference(),
|
||||
ellipsis = TextEllipsis.Middle,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
|
||||
EllipsisText(
|
||||
text = address,
|
||||
ellipsis = TextEllipsis.Middle,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption2,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerW12()
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
innerPadding = 6.dp,
|
||||
iconRes = R.drawable.ic_qrcode_new_24,
|
||||
onClick = onOpenQrCodeClick,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
iconRes = R.drawable.ic_copy_new_24,
|
||||
innerPadding = 6.dp,
|
||||
onClick = onCopyClick,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerW12()
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
iconRes = R.drawable.ic_qrcode_new_24,
|
||||
onClick = onOpenQrCodeClick,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
iconRes = R.drawable.ic_copy_new_24,
|
||||
onClick = onCopyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -275,8 +278,9 @@ private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier
|
|||
)
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(28.dp),
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
iconRes = R.drawable.ic_copy_new_24,
|
||||
innerPadding = 6.dp,
|
||||
onClick = onCopyClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -318,7 +322,7 @@ private fun Preview_TokenReceiveAssetsContent(
|
|||
private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<ReceiveAssetsUM> {
|
||||
val address = ReceiveAddress(
|
||||
value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d",
|
||||
type = ReceiveAddress.Type.Default(
|
||||
type = ReceiveAddress.Type.Primary.Default(
|
||||
displayName = stringReference("Etherium address"),
|
||||
),
|
||||
)
|
||||
|
|
@ -338,7 +342,6 @@ private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<Recei
|
|||
onCopyClick = {},
|
||||
onOpenQrCodeClick = {},
|
||||
isEnsResultLoading = true,
|
||||
fullName = "Etherium",
|
||||
)
|
||||
|
||||
override val values: Sequence<ReceiveAssetsUM>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.tokenreceive.ui
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -83,7 +84,7 @@ internal fun TokenReceiveContent(
|
|||
) {
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(fade()),
|
||||
animation = stackAnimation(fade(animationSpec = tween(durationMillis = 100))),
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.animateContentSize(),
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@ package com.tangem.features.tokenreceive.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -21,6 +18,7 @@ import com.tangem.core.ui.components.SecondaryButton
|
|||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
|
|
@ -46,15 +44,17 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) {
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.size(size = 56.dp),
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.size(size = 64.dp),
|
||||
state = warningUM.iconState,
|
||||
shouldDisplayNetwork = false,
|
||||
shouldDisplayNetwork = true,
|
||||
iconSize = 56.dp,
|
||||
)
|
||||
|
||||
SpacerH24()
|
||||
|
||||
WarningBlock(networkIcon = warningUM.networkIcon, networkName = warningUM.network)
|
||||
WarningBlock(networkName = warningUM.network)
|
||||
|
||||
SpacerH12()
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun WarningBlock(networkName: String, networkIcon: Int, modifier: Modifier = Modifier) {
|
||||
fun WarningBlock(networkName: String, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
@ -91,21 +91,14 @@ fun WarningBlock(networkName: String, networkIcon: Int, modifier: Modifier = Mod
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
painter = painterResource(id = networkIcon),
|
||||
tint = Color.Unspecified,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerW6()
|
||||
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +129,6 @@ private class TokenReceiveWarningContentProvider : PreviewParameterProvider<Warn
|
|||
iconState = iconState,
|
||||
onWarningAcknowledged = {},
|
||||
network = "Etherium",
|
||||
networkIcon = R.drawable.ic_eth_16,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -12,5 +12,4 @@ internal data class ReceiveAssetsUM(
|
|||
val onCopyClick: (id: Int) -> Unit,
|
||||
val isEnsResultLoading: Boolean,
|
||||
val notificationConfigs: ImmutableList<NotificationUM>,
|
||||
val fullName: String,
|
||||
)
|
||||
|
|
@ -4,7 +4,6 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
|||
|
||||
internal data class WarningUM(
|
||||
val network: String,
|
||||
val networkIcon: Int,
|
||||
val iconState: CurrencyIconState,
|
||||
val onWarningAcknowledged: () -> Unit,
|
||||
)
|
||||
|
|
@ -58,6 +58,8 @@ import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
|||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
|
|
@ -876,7 +878,13 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
|
||||
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
|
||||
clipboardManager.setText(text = defaultAddress, isSensitive = true)
|
||||
analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol))
|
||||
analyticsEventsHandler.send(
|
||||
TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchainName = cryptoCurrency.network.name,
|
||||
tokenReceiveSource = TokenReceiveCopyActionSource.Token,
|
||||
),
|
||||
)
|
||||
return resourceReference(R.string.wallet_notification_address_copied)
|
||||
}
|
||||
|
||||
|
|
@ -1089,16 +1097,17 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
displayName = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
addresses.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Default,
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
displayName = "${cryptoCurrency.name} (${cryptoCurrency.symbol})",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import com.tangem.domain.tokens.RemoveCurrencyUseCase
|
|||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
|
||||
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
|
|
@ -174,10 +176,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
|
||||
event?.let { analyticsEventHandler.send(it) }
|
||||
|
||||
if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) {
|
||||
|
|
@ -188,6 +186,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol),
|
||||
)
|
||||
stateHolder.showBottomSheet(
|
||||
createReceiveBottomSheetContent(
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
|
|
@ -206,7 +207,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
|
||||
clipboardManager.setText(text = defaultAddress, isSensitive = true)
|
||||
analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol))
|
||||
analyticsEventHandler.send(
|
||||
TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchainName = cryptoCurrency.network.name,
|
||||
tokenReceiveSource = TokenReceiveCopyActionSource.Main,
|
||||
),
|
||||
)
|
||||
return resourceReference(R.string.wallet_notification_address_copied)
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +242,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onCopyAddressClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
analyticsEventHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.ButtonCopyAddress(cryptoCurrencyStatus.currency.symbol),
|
||||
event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
|
||||
token = cryptoCurrencyStatus.currency.symbol,
|
||||
blockchainName = cryptoCurrencyStatus.currency.network.name,
|
||||
tokenReceiveSource = TokenReceiveCopyActionSource.Main,
|
||||
),
|
||||
)
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
|
|
@ -673,16 +684,17 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Ens,
|
||||
value = ens,
|
||||
displayName = ens,
|
||||
),
|
||||
)
|
||||
}
|
||||
networkAddress.availableAddresses.map { address ->
|
||||
add(
|
||||
ReceiveAddressModel(
|
||||
nameService = ReceiveAddressModel.NameService.Default,
|
||||
nameService = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
|
||||
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
|
||||
},
|
||||
value = address.value,
|
||||
displayName = "${cryptoCurrencyStatus.currency.name} (${cryptoCurrencyStatus.currency.symbol})",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:Suppress("FunctionSignature")
|
||||
|
||||
package com.tangem.feature.wallet.presentation.wallet.deeplink
|
||||
|
||||
import arrow.core.Either
|
||||
|
|
@ -354,80 +356,82 @@ class DefaultPromoDeeplinkHandlerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN BTC status but currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to "PROMO123")
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz")
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatus))
|
||||
val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum")
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly)
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
fun `GIVEN BTC status but currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to "PROMO123")
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz")
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatus))
|
||||
val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum")
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly)
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
}
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple statuses emissions and currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN multiple statuses emissions and currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val ethStatus = buildNetworkStatus(rawNetworkId = "ethereum", address = "0x123")
|
||||
val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz")
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flow {
|
||||
emit(emptySet())
|
||||
emit(setOf(ethStatus))
|
||||
emit(setOf(ethStatus, btcStatus))
|
||||
}
|
||||
val ethStatus = buildNetworkStatus(rawNetworkId = "ethereum", address = "0x123")
|
||||
val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz")
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flow {
|
||||
emit(emptySet())
|
||||
emit(setOf(ethStatus))
|
||||
emit(setOf(ethStatus, btcStatus))
|
||||
}
|
||||
|
||||
val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum")
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly)
|
||||
val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum")
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two BTC statuses with different derivation AND two BTC currencies WHEN activate THEN activated`() =
|
||||
|
|
@ -485,270 +489,275 @@ class DefaultPromoDeeplinkHandlerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two BTC statuses with different derivation AND one matching BTC currency WHEN activate THEN activated`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN two BTC statuses with different derivation AND one matching BTC currency WHEN activate THEN activated`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom))
|
||||
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard)
|
||||
|
||||
coEvery { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } returns Either.Right("ok")
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success))
|
||||
|
||||
coVerify(exactly = 1) { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) }
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke("bc1qcustom", promoCode) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.Activated),
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom))
|
||||
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard)
|
||||
|
||||
coEvery { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } returns Either.Right("ok")
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success))
|
||||
|
||||
coVerify(exactly = 1) { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) }
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke("bc1qcustom", promoCode) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.Activated),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two BTC statuses with different derivation AND no BTC currencies WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN two BTC statuses with different derivation AND no BTC currencies WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom))
|
||||
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns emptySet()
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom))
|
||||
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns emptySet()
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two BTC currencies first derivation mismatched AND one matching status WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN two BTC currencies first derivation mismatched AND one matching status WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard))
|
||||
|
||||
val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom)
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(
|
||||
btcCoinCustom,
|
||||
btcCoinCard,
|
||||
)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard))
|
||||
|
||||
val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom)
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(
|
||||
btcCoinCustom,
|
||||
btcCoinCard,
|
||||
)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN only BTC status with CUSTOM derivation AND only BTC currency with CARD derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN only BTC status with CUSTOM derivation AND only BTC currency with CARD derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCustom))
|
||||
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
val btcStatusCustom = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcustom",
|
||||
derivationPath = dpCustom,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCustom))
|
||||
|
||||
val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN only BTC status with CARD derivation AND only BTC currency with CUSTOM derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
fun `GIVEN only BTC status with CARD derivation AND only BTC currency with CUSTOM derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() =
|
||||
runTest {
|
||||
val promoCode = "PROMO123"
|
||||
val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode)
|
||||
val userWallet = mockUserWallet("ABCDEF")
|
||||
every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet)
|
||||
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
val dpCard = Network.DerivationPath.Card("m/44'/0'/0'")
|
||||
val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'")
|
||||
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard))
|
||||
|
||||
val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCustom)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
val btcStatusCard = buildNetworkStatus(
|
||||
rawNetworkId = Blockchain.Bitcoin.id,
|
||||
address = "bc1qcard",
|
||||
derivationPath = dpCard,
|
||||
)
|
||||
coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard))
|
||||
|
||||
val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom)
|
||||
coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCustom)
|
||||
|
||||
val dispatcherProvider = testDispatcherProvider(testScheduler)
|
||||
|
||||
DefaultPromoDeeplinkHandler(
|
||||
scope = this,
|
||||
queryParams = queryParams,
|
||||
uiMessageSender = uiMessageSender,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
val sent = messages.last { it is DialogMessage } as DialogMessage
|
||||
Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title))
|
||||
Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address))
|
||||
|
||||
coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) }
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) }
|
||||
verify(exactly = 1) {
|
||||
analyticsEventHandler.send(
|
||||
PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mockUserWallet(id: String): UserWallet {
|
||||
val userWallet = mockk<UserWallet>(relaxed = true)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
# https://github.com/tangem/tangem-sdk-android/
|
||||
# https://github.com/tangem/vico
|
||||
|
||||
tangemBlockchainSdk = "develop-1205"
|
||||
tangemBlockchainSdk = "develop-1212"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-557"
|
||||
tangemCardSdk = "develop-560"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
tangemVico = "2.0.0-alpha.25-tangem12"
|
||||
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
|||
"zklink/test" -> Blockchain.ZkLinkNovaTestnet
|
||||
"pepecoin" -> Blockchain.Pepecoin
|
||||
"pepecoin/test" -> Blockchain.PepecoinTestnet
|
||||
"hyperliquid" -> Blockchain.Hyperliquid
|
||||
"hyperliquid/test" -> Blockchain.HyperliquidTestnet
|
||||
"hyperevm" -> Blockchain.Hyperliquid
|
||||
"hyperevm/test" -> Blockchain.HyperliquidTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
@ -329,8 +329,8 @@ fun Blockchain.toNetworkId(): String {
|
|||
Blockchain.ZkLinkNovaTestnet -> "zklink/test"
|
||||
Blockchain.Pepecoin -> "pepecoin"
|
||||
Blockchain.PepecoinTestnet -> "pepecoin/test"
|
||||
Blockchain.Hyperliquid -> "hyperliquid"
|
||||
Blockchain.HyperliquidTestnet -> "hyperliquid/test"
|
||||
Blockchain.Hyperliquid -> "hyperevm"
|
||||
Blockchain.HyperliquidTestnet -> "hyperevm/test"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue