Updated on 2026-08-14
This commit is contained in:
commit
38572c0a4e
89 changed files with 738 additions and 390 deletions
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class DetailsScreen(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Details Screen", event, params) {
|
||||
|
||||
class ScreenOpened : DetailsScreen("Details Screen Opened")
|
||||
}
|
||||
|
|
@ -40,19 +40,19 @@ object AmountStatePreviewData {
|
|||
appCurrency = AppCurrency.Default,
|
||||
tokenName = stringReference("Tether"),
|
||||
amountTextField = AmountFieldModel(
|
||||
value = "",
|
||||
value = "2 130,81231238",
|
||||
onValueChange = {},
|
||||
keyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions = KeyboardActions.Default,
|
||||
cryptoAmount = Amount(
|
||||
currencySymbol = "USDT",
|
||||
value = BigDecimal.ZERO,
|
||||
value = "2112312330.81212331238".toBigDecimal(),
|
||||
decimals = 18,
|
||||
type = AmountType.CoinType,
|
||||
),
|
||||
fiatAmount = Amount(
|
||||
currencySymbol = "$",
|
||||
value = BigDecimal.ZERO,
|
||||
value = "2111232330.81".toBigDecimal(),
|
||||
decimals = 2,
|
||||
type = AmountType.CoinType,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ private fun AmountBlockV2(
|
|||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
modifier = Modifier.padding(top = 8.dp).weight(1f),
|
||||
) {
|
||||
ResizableText(
|
||||
text = firstAmount,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
{
|
||||
"name": "STAKING_CARDANO_ENABLED",
|
||||
"version": "5.28.0"
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "WALLET_CONNECT_REDESIGN_ENABLED",
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ interface TangemExpressApi {
|
|||
@Query("requestId") requestId: String,
|
||||
@Query("refundAddress") refundAddress: String?, // for cex only
|
||||
@Query("refundExtraId") refundExtraId: String?, // for cex only
|
||||
@Query("partnerOperationType") partnerOperationType: String?, // swap/ swap-and-send
|
||||
): ApiResponse<ExchangeDataResponse>
|
||||
|
||||
@GET("exchange-status")
|
||||
|
|
|
|||
|
|
@ -394,6 +394,7 @@
|
|||
<string name="domain_receive_assets_default_address">Standardadresse</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy adresse</string>
|
||||
<string name="domain_receive_assets_navigation_title">Empfangen von Vermögenswerten</string>
|
||||
<string name="domain_receive_assets_network_name_address">%s Adresse</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Das Senden von Vermögenswerten in anderen Netzwerken führt zu dauerhaftem Verlust.</string>
|
||||
<string name="domain_receive_assets_onboarding_network_name">%s Netzwerk</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Sende Geld nur mit</string>
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@
|
|||
<string name="domain_receive_assets_default_address">Dirección por defecto</string>
|
||||
<string name="domain_receive_assets_legacy_address">Dirección Legacy</string>
|
||||
<string name="domain_receive_assets_navigation_title">Recibir activos</string>
|
||||
<string name="domain_receive_assets_network_name_address">%s dirección</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Enviar activos a otras redes resultará en una pérdida permanente.</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Envíe fondos utilizando solo</string>
|
||||
<string name="email_preface_wc_error">Hola equipo de soporte, he encontrado un error con el código: %s</string>
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@
|
|||
<string name="domain_receive_assets_default_address">Adresse par défaut</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy adresse</string>
|
||||
<string name="domain_receive_assets_navigation_title">Recevoir des actifs</string>
|
||||
<string name="domain_receive_assets_network_name_address">%s adresse</string>
|
||||
<string name="domain_receive_assets_onboarding_description">L’envoi d’actifs sur d’autres réseaux entraînera une perte définitive.</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Envoyez des fonds en utilisant uniquement</string>
|
||||
<string name="email_preface_wc_error">Bonjour équipe de support, j’ai rencontré une erreur avec le code : %s</string>
|
||||
|
|
|
|||
|
|
@ -314,6 +314,7 @@
|
|||
<string name="domain_receive_assets_default_address">Основной адрес</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy адрес</string>
|
||||
<string name="domain_receive_assets_navigation_title">Получить активы</string>
|
||||
<string name="domain_receive_assets_network_name_address">%s адрес</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Отправка средств в другой сети может повлечь потерю средств.</string>
|
||||
<string name="domain_receive_assets_onboarding_network_name">%s сети</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Отправляйте средства, используя только</string>
|
||||
|
|
|
|||
|
|
@ -305,6 +305,7 @@
|
|||
<string name="domain_receive_assets_default_address">Основна адреса</string>
|
||||
<string name="domain_receive_assets_legacy_address">Legacy адреса</string>
|
||||
<string name="domain_receive_assets_navigation_title">Отримати активи</string>
|
||||
<string name="domain_receive_assets_network_name_address">%s адреса</string>
|
||||
<string name="domain_receive_assets_onboarding_description">Надсилання активів в інші мережі призведе до безповоротної втрати.</string>
|
||||
<string name="domain_receive_assets_onboarding_title">Надсилайте кошти, використовуючи лише</string>
|
||||
<string name="email_preface_wc_error">Привіт, команда підтримки, я зіткнувся з помилкою з кодом: %s</string>
|
||||
|
|
|
|||
|
|
@ -395,6 +395,7 @@
|
|||
<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_network_name_address">%s address</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>
|
||||
<string name="domain_receive_assets_onboarding_title">Send funds using only</string>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import com.tangem.datasource.api.tangemTech.models.GeoResponse
|
|||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.settings.usercountry.models.GB_COUNTRY
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -56,7 +58,14 @@ internal class DefaultSettingsRepository(
|
|||
appLogsStore.deleteDeprecatedLogs(maxSize)
|
||||
}
|
||||
|
||||
override suspend fun isSendTapHelpPreviewEnabled(): Boolean {
|
||||
override fun isSendTapHelpPreviewEnabled(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(
|
||||
key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY,
|
||||
default = true,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun isSendTapHelpPreviewEnabledSync(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.SEND_TAP_HELP_PREVIEW_KEY,
|
||||
default = true,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ import com.tangem.datasource.di.NetworkMoshi
|
|||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.express.ExpressRepository
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.express.models.*
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
|
|
@ -96,13 +93,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
it.currency.network.backendId == pair.to.network
|
||||
}
|
||||
|
||||
if (statusFrom != null && statusTo != null) {
|
||||
val mappedProviders = pair.providers.mapNotNull {
|
||||
mappedProviders[it.providerId]
|
||||
}
|
||||
|
||||
if (statusFrom != null && statusTo != null && mappedProviders.isNotEmpty()) {
|
||||
SwapPairModel(
|
||||
from = statusFrom,
|
||||
to = statusTo,
|
||||
providers = pair.providers.mapNotNull {
|
||||
mappedProviders[it.providerId]
|
||||
},
|
||||
providers = mappedProviders,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -151,13 +150,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
val currencyStatusFrom = createSendWithSwapCryptoCurrencyStatus(statusFromDeferred.await())
|
||||
val currencyStatusTo = createSendWithSwapCryptoCurrencyStatus(statusToDeferred.await())
|
||||
|
||||
if (currencyStatusFrom != null && currencyStatusTo != null) {
|
||||
val mappedProvider = pair.providers.mapNotNull {
|
||||
mappedProviders[it.providerId]
|
||||
}
|
||||
|
||||
if (currencyStatusFrom != null && currencyStatusTo != null && mappedProvider.isNotEmpty()) {
|
||||
SwapPairModel(
|
||||
from = currencyStatusFrom,
|
||||
to = currencyStatusTo,
|
||||
providers = pair.providers.mapNotNull {
|
||||
mappedProviders[it.providerId]
|
||||
},
|
||||
providers = mappedProvider,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -208,6 +209,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
toAddress: String,
|
||||
expressProvider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapDataModel = withContext(coroutineDispatcher.io) {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val fromCryptoCurrency = fromCryptoCurrencyStatus.currency
|
||||
|
|
@ -239,6 +241,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
refundAddress = refundData?.refundAddress,
|
||||
refundExtraId = refundData?.refundExtraId,
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
partnerOperationType = expressOperationType.value,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
|
|
|
|||
|
|
@ -356,10 +356,6 @@ internal class DefaultCurrenciesRepository(
|
|||
getMultiCurrencyWalletCurrencies(userWallet)
|
||||
.onEach { send(it) }
|
||||
.launchIn(scope = this + dispatchers.io)
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchTokensIfCacheExpired(userWallet, refresh = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,12 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
).map { settledSession ->
|
||||
val newSession = WcSession(
|
||||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(settledSession.session),
|
||||
sdkModel = WcSdkSessionConverter.convert(
|
||||
value = WcSdkSessionConverter.Input(
|
||||
originUrl = sdkVerifyContext.origin,
|
||||
session = settledSession.session,
|
||||
),
|
||||
),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
networks = sessionForApprove.network.toSet(),
|
||||
connectingTime = DateTime.now().millis,
|
||||
|
|
@ -109,8 +114,9 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
sessionsManager.saveSession(newSession)
|
||||
analytics.send(
|
||||
WcAnalyticEvents.DAppConnected(
|
||||
proposalState.dAppSession,
|
||||
sessionForApprove,
|
||||
sessionProposal = proposalState.dAppSession,
|
||||
sessionForApprove = sessionForApprove,
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
),
|
||||
)
|
||||
newSession
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
ifRight = { result ->
|
||||
when (result) {
|
||||
is Wallet.Model.SettledSessionResponse.Result -> result.right()
|
||||
is Wallet.Model.SettledSessionResponse.Error ->
|
||||
ApprovalFailed(result.errorMessage).left()
|
||||
is Wallet.Model.SettledSessionResponse.Error -> ApprovalFailed(result.errorMessage).left()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -113,8 +112,9 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
verifyContext: Wallet.Model.VerifyContext,
|
||||
) {
|
||||
val sessionProposalWithRealUrl = sessionProposal.copy(url = verifyContext.origin)
|
||||
// Triggered when wallet receives the session proposal sent by a Dapp
|
||||
onSessionProposal.trySend(sessionProposal to verifyContext)
|
||||
onSessionProposal.trySend(sessionProposalWithRealUrl to verifyContext)
|
||||
}
|
||||
|
||||
override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) {
|
||||
|
|
@ -147,7 +147,7 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
else -> WcPairError.PairingFailed(this.localizedMessage.orEmpty())
|
||||
}
|
||||
|
||||
private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left()
|
||||
private fun Throwable.toApproveError() = ApprovalFailed(this.localizedMessage.orEmpty()).left()
|
||||
|
||||
companion object {
|
||||
private const val CALLBACK_TIMEOUT = 15
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ internal class DefaultWcRequestService(
|
|||
verifyContext: Wallet.Model.VerifyContext,
|
||||
) {
|
||||
// Triggered when a Dapp sends SessionRequest to sign a transaction or a message
|
||||
val sr = WcSdkSessionRequestConverter.convert(sessionRequest)
|
||||
val sr = WcSdkSessionRequestConverter.convert(
|
||||
WcSdkSessionRequestConverter.Input(
|
||||
originUrl = verifyContext.origin,
|
||||
sessionRequest = sessionRequest,
|
||||
),
|
||||
)
|
||||
Timber.tag(WC_TAG).i("handle request $sr")
|
||||
val name = requestConverters.firstNotNullOfOrNull { it.toWcMethodName(sr) }
|
||||
?: WcMethodName.Unsupported(sr.request.method)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ internal class DefaultWcSessionsManager(
|
|||
WcSessionDTO(
|
||||
topic = session.sdkModel.topic,
|
||||
walletId = session.wallet.walletId,
|
||||
url = session.sdkModel.appMetaData.url,
|
||||
securityStatus = session.securityStatus,
|
||||
connectingTime = session.connectingTime ?: DateTime.now().millis,
|
||||
),
|
||||
|
|
@ -105,8 +106,15 @@ internal class DefaultWcSessionsManager(
|
|||
.map { walletId ->
|
||||
flow {
|
||||
emit(
|
||||
legacyStore.loadSessions(walletId.stringValue).map {
|
||||
WcSessionDTO(it.topic, walletId, CheckDAppResult.FAILED_TO_VERIFY)
|
||||
legacyStore.loadSessions(walletId.stringValue).mapNotNull { legacySession ->
|
||||
val url =
|
||||
inSdk.find { it.topic == legacySession.topic }?.metaData?.url ?: return@mapNotNull null
|
||||
WcSessionDTO(
|
||||
topic = legacySession.topic,
|
||||
walletId = walletId,
|
||||
url = url,
|
||||
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -130,9 +138,15 @@ internal class DefaultWcSessionsManager(
|
|||
val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null
|
||||
val networks = wcNetworksConverter.findWalletNetworks(wallet, sdkSession)
|
||||
val originUrl = storeSession.url ?: sdkSession.metaData?.url ?: ""
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
sdkModel = WcSdkSessionConverter.convert(
|
||||
value = WcSdkSessionConverter.Input(
|
||||
originUrl = originUrl,
|
||||
session = sdkSession,
|
||||
),
|
||||
),
|
||||
securityStatus = storeSession.securityStatus,
|
||||
networks = networks,
|
||||
connectingTime = storeSession.connectingTime,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
|
|||
WcAnalyticEvents.SignatureRequestHandled(
|
||||
rawRequest = context.rawSdkRequest,
|
||||
network = context.network,
|
||||
securityStatus = context.session.securityStatus,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.reown.android.Core
|
|||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object WcAppMetaDataConverter : Converter<Core.Model.AppMetaData, WcAppMetaData> {
|
||||
internal object WcAppMetaDataConverter : Converter<WcAppMetaDataConverter.Input, WcAppMetaData> {
|
||||
|
||||
val empty
|
||||
get() = WcAppMetaData(
|
||||
|
|
@ -18,16 +18,18 @@ internal object WcAppMetaDataConverter : Converter<Core.Model.AppMetaData, WcApp
|
|||
verifyUrl = null,
|
||||
)
|
||||
|
||||
override fun convert(value: Core.Model.AppMetaData): WcAppMetaData {
|
||||
override fun convert(value: Input): WcAppMetaData {
|
||||
return WcAppMetaData(
|
||||
name = value.name,
|
||||
description = value.description,
|
||||
url = value.url,
|
||||
icons = value.icons,
|
||||
redirect = value.redirect,
|
||||
appLink = value.appLink,
|
||||
linkMode = value.linkMode,
|
||||
verifyUrl = value.verifyUrl,
|
||||
name = value.peerMetaData.name,
|
||||
description = value.peerMetaData.description,
|
||||
url = value.originUrl,
|
||||
icons = value.peerMetaData.icons,
|
||||
redirect = value.peerMetaData.redirect,
|
||||
appLink = value.peerMetaData.appLink,
|
||||
linkMode = value.peerMetaData.linkMode,
|
||||
verifyUrl = value.peerMetaData.verifyUrl,
|
||||
)
|
||||
}
|
||||
|
||||
internal data class Input(val originUrl: String, val peerMetaData: Core.Model.AppMetaData)
|
||||
}
|
||||
|
|
@ -4,13 +4,22 @@ import com.reown.walletkit.client.Wallet
|
|||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object WcSdkSessionConverter : Converter<Wallet.Model.Session, WcSdkSession> {
|
||||
internal object WcSdkSessionConverter : Converter<WcSdkSessionConverter.Input, WcSdkSession> {
|
||||
|
||||
override fun convert(value: Wallet.Model.Session): WcSdkSession {
|
||||
override fun convert(value: Input): WcSdkSession {
|
||||
return WcSdkSession(
|
||||
topic = value.topic,
|
||||
appMetaData = value.metaData?.let { WcAppMetaDataConverter.convert(it) } ?: WcAppMetaDataConverter.empty,
|
||||
namespaces = value.namespaces.mapValues { (_, session) ->
|
||||
topic = value.session.topic,
|
||||
appMetaData = value.session.metaData
|
||||
?.let {
|
||||
WcAppMetaDataConverter.convert(
|
||||
value = WcAppMetaDataConverter.Input(
|
||||
originUrl = value.originUrl,
|
||||
peerMetaData = it,
|
||||
),
|
||||
)
|
||||
}
|
||||
?: WcAppMetaDataConverter.empty,
|
||||
namespaces = value.session.namespaces.mapValues { (_, session) ->
|
||||
WcSdkSession.Session(
|
||||
chains = session.chains ?: listOf(),
|
||||
accounts = session.accounts,
|
||||
|
|
@ -20,4 +29,6 @@ internal object WcSdkSessionConverter : Converter<Wallet.Model.Session, WcSdkSes
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal data class Input(val originUrl: String, val session: Wallet.Model.Session)
|
||||
}
|
||||
|
|
@ -4,15 +4,25 @@ import com.reown.walletkit.client.Wallet
|
|||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object WcSdkSessionRequestConverter : Converter<Wallet.Model.SessionRequest, WcSdkSessionRequest> {
|
||||
internal object WcSdkSessionRequestConverter : Converter<WcSdkSessionRequestConverter.Input, WcSdkSessionRequest> {
|
||||
|
||||
override fun convert(value: Wallet.Model.SessionRequest): WcSdkSessionRequest {
|
||||
override fun convert(value: Input): WcSdkSessionRequest {
|
||||
return WcSdkSessionRequest(
|
||||
topic = value.topic,
|
||||
chainId = value.chainId,
|
||||
dAppMetaData = value.peerMetaData?.let { WcAppMetaDataConverter.convert(it) }
|
||||
topic = value.sessionRequest.topic,
|
||||
chainId = value.sessionRequest.chainId,
|
||||
dAppMetaData = value.sessionRequest.peerMetaData
|
||||
?.let {
|
||||
WcAppMetaDataConverter.convert(
|
||||
value = WcAppMetaDataConverter.Input(
|
||||
originUrl = value.originUrl,
|
||||
peerMetaData = it,
|
||||
),
|
||||
)
|
||||
}
|
||||
?: WcAppMetaDataConverter.empty,
|
||||
request = JSONRPCRequestConverter.convert(value.request),
|
||||
request = JSONRPCRequestConverter.convert(value.sessionRequest.request),
|
||||
)
|
||||
}
|
||||
|
||||
internal data class Input(val originUrl: String, val sessionRequest: Wallet.Model.SessionRequest)
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
private val sdkVerifyContext: Wallet.Model.VerifyContext
|
||||
get() = Wallet.Model.VerifyContext(
|
||||
id = 9259,
|
||||
origin = "vituperata",
|
||||
origin = "https://react-app.walletconnect.com/",
|
||||
validation = Wallet.Model.Validation.VALID,
|
||||
verifyUrl = "https://search.yahoo.com/search?p=id",
|
||||
isScam = false,
|
||||
|
|
@ -68,6 +68,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
|
||||
private val unsupportedDAppUrl = "dydx.trade"
|
||||
private val unsupportedSdkProposal get() = sdkProposal.copy(url = unsupportedDAppUrl)
|
||||
private val unsupportedSdkVerifyContext get() = sdkVerifyContext.copy(origin = unsupportedDAppUrl)
|
||||
|
||||
private val sessionForApprove: WcSessionApprove
|
||||
get() = WcSessionApprove(
|
||||
|
|
@ -100,7 +101,12 @@ internal class DefaultWcPairUseCaseTest {
|
|||
private val Wallet.Model.Session.sessionForSave: WcSession
|
||||
get() = WcSession(
|
||||
wallet = sessionForApprove.wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
sdkModel = WcSdkSessionConverter.convert(
|
||||
value = WcSdkSessionConverter.Input(
|
||||
originUrl = sdkVerifyContext.origin,
|
||||
session = this,
|
||||
),
|
||||
),
|
||||
securityStatus = CheckDAppResult.SAFE,
|
||||
networks = setOf(),
|
||||
connectingTime = null,
|
||||
|
|
@ -138,7 +144,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
assertEquals(loading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.pair(url)
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkProposal.url))
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin))
|
||||
}
|
||||
assert(awaitItem() is WcPairState.Proposal)
|
||||
expectNoEvents()
|
||||
|
|
@ -161,7 +167,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
assertEquals(loading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.pair(url)
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkProposal.url))
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin))
|
||||
}
|
||||
assert(awaitItem() is WcPairState.Proposal)
|
||||
useCase.approve(sessionForApprove)
|
||||
|
|
@ -194,7 +200,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
assertEquals(loading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.pair(url)
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkProposal.url))
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin))
|
||||
}
|
||||
assert(awaitItem() is WcPairState.Proposal)
|
||||
useCase.reject()
|
||||
|
|
@ -207,7 +213,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `success pair and reject unsupported dApp`() = runTest {
|
||||
coEvery { sdkDelegate.pair(url) } returns (unsupportedSdkProposal to sdkVerifyContext).right()
|
||||
coEvery { sdkDelegate.pair(url) } returns (unsupportedSdkProposal to unsupportedSdkVerifyContext).right()
|
||||
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDApp(unsupportedSdkProposal.name))
|
||||
|
||||
val useCase = useCaseFactory()
|
||||
|
|
@ -255,7 +261,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
coVerifyOrder {
|
||||
sdkDelegate.pair(url)
|
||||
associateNetworksDelegate.associate(sdkProposal)
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkProposal.url))
|
||||
blockAidVerifier.verifyDApp(DAppData(sdkVerifyContext.origin))
|
||||
}
|
||||
assert(awaitItem() is WcPairState.Proposal)
|
||||
useCase.approve(sessionForApprove)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.express.models
|
||||
|
||||
enum class ExpressOperationType(val value: String) {
|
||||
SWAP("swap"),
|
||||
SEND_WITH_SWAP("swap-and-send"),
|
||||
}
|
||||
|
|
@ -10,5 +10,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository
|
|||
*/
|
||||
class IsSendTapHelpEnabledUseCase(private val settingsRepository: SettingsRepository) {
|
||||
|
||||
suspend operator fun invoke() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabled() }
|
||||
operator fun invoke() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabled() }
|
||||
|
||||
suspend fun invokeSync() = Either.catch { settingsRepository.isSendTapHelpPreviewEnabledSync() }
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.domain.settings.repositories
|
||||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -16,7 +17,9 @@ interface SettingsRepository {
|
|||
|
||||
fun deleteDeprecatedLogs(maxSize: Int)
|
||||
|
||||
suspend fun isSendTapHelpPreviewEnabled(): Boolean
|
||||
fun isSendTapHelpPreviewEnabled(): Flow<Boolean>
|
||||
|
||||
suspend fun isSendTapHelpPreviewEnabledSync(): Boolean
|
||||
|
||||
suspend fun setSendTapHelpPreviewAvailability(isEnabled: Boolean)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.swap
|
||||
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
|
|
@ -74,6 +75,7 @@ interface SwapRepositoryV2 {
|
|||
* @param toAddress destination address
|
||||
* @param expressProvider selected swap provider
|
||||
* @param rateType selected provider rate type
|
||||
* @param expressOperationType operation type swap or send with swap
|
||||
*/
|
||||
suspend fun getSwapData(
|
||||
userWallet: UserWallet,
|
||||
|
|
@ -83,6 +85,7 @@ interface SwapRepositoryV2 {
|
|||
toAddress: String,
|
||||
expressProvider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapDataModel
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.swap.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -25,6 +26,7 @@ class GetSwapDataUseCase(
|
|||
toAddress: String,
|
||||
expressProvider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): Either<ExpressError, SwapDataModel> = Either.catch {
|
||||
swapRepositoryV2.getSwapData(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -34,6 +36,7 @@ class GetSwapDataUseCase(
|
|||
toAddress = toAddress,
|
||||
expressProvider = expressProvider,
|
||||
rateType = rateType,
|
||||
expressOperationType = expressOperationType,
|
||||
)
|
||||
}.mapLeft { throwable ->
|
||||
swapErrorResolver.resolve(throwable)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.tokens.model.analytics
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BALANCE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
|
@ -16,11 +17,26 @@ sealed class TokenScreenAnalyticsEvent(
|
|||
) : AnalyticsEvent("Token", event, params) {
|
||||
|
||||
/** Legacy event. It has a unique category, but it also is sent on TokenScreen */
|
||||
class DetailsScreenOpened(token: String) : AnalyticsEvent(
|
||||
class DetailsScreenOpened(
|
||||
blockchain: String,
|
||||
token: String,
|
||||
tokenBalance: TokenBalance,
|
||||
) : AnalyticsEvent(
|
||||
category = "Details Screen",
|
||||
event = "Details Screen Opened",
|
||||
params = mapOf("Token" to token),
|
||||
)
|
||||
params = mapOf(
|
||||
BLOCKCHAIN to blockchain,
|
||||
TOKEN_PARAM to token,
|
||||
BALANCE to tokenBalance.name,
|
||||
),
|
||||
) {
|
||||
sealed class TokenBalance(val name: String) {
|
||||
data object Full : TokenBalance("Full")
|
||||
data object Error : TokenBalance("Error")
|
||||
data object Empty : TokenBalance("Empty")
|
||||
data object NoAddress : TokenBalance("No Address")
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonRemoveToken(token: String) : TokenScreenAnalyticsEvent(
|
||||
"Button - Remove Token",
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ sealed class WcPairError(
|
|||
override val message: String = "",
|
||||
) : Exception() {
|
||||
|
||||
data class UriAlreadyUsed(override val message: String) : WcPairError("107 001 001")
|
||||
data class PairingFailed(override val message: String) : WcPairError("107 001 002")
|
||||
data object InvalidDomainURL : WcPairError("107 001 003")
|
||||
data class UnsupportedDApp(val appName: String) : WcPairError("107 001 004")
|
||||
data class UnsupportedBlockchains(val chains: Set<String>, val appName: String) : WcPairError("107 001 005")
|
||||
data class UriAlreadyUsed(override val message: String) : WcPairError(code = "107 001 001")
|
||||
data class PairingFailed(override val message: String) : WcPairError(code = "107 001 002")
|
||||
data object InvalidDomainURL : WcPairError(code = "107 001 003")
|
||||
|
||||
data object InvalidConnectionRequest : WcPairError("107 002 001")
|
||||
data object ProposalExpired : WcPairError("107 002 002")
|
||||
data class ApprovalFailed(override val message: String) : WcPairError("107 002 003")
|
||||
data object RejectionFailed : WcPairError("107 002 004")
|
||||
data class Unknown(override val message: String) : WcPairError(message)
|
||||
data class TimeoutException(override val message: String) : WcPairError(message)
|
||||
data class UnsupportedDApp(val appName: String) : WcPairError(code = "107 001 004")
|
||||
data class UnsupportedBlockchains(val chains: Set<String>, val appName: String) : WcPairError(code = "107 001 005")
|
||||
|
||||
data object InvalidConnectionRequest : WcPairError(code = "107 002 001")
|
||||
data object ProposalExpired : WcPairError(code = "107 002 002")
|
||||
data class ApprovalFailed(override val message: String) : WcPairError(code = "107 002 003")
|
||||
data object RejectionFailed : WcPairError(code = "107 002 004")
|
||||
data class Unknown(override val message: String) : WcPairError(code = "107 003 001")
|
||||
data class TimeoutException(override val message: String) : WcPairError(code = "107 003 002")
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
data class WcSessionDTO(
|
||||
val topic: String,
|
||||
val walletId: UserWalletId,
|
||||
val url: String?,
|
||||
val securityStatus: CheckDAppResult = CheckDAppResult.FAILED_TO_VERIFY,
|
||||
val connectingTime: Long? = null,
|
||||
)
|
||||
|
|
@ -5,6 +5,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult.*
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents.ButtonDisconnectAll.toAnalyticVerificationStatus
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
|
|
@ -42,11 +43,7 @@ sealed class WcAnalyticEvents(
|
|||
event = "dApp Connection Requested",
|
||||
params = mapOf(
|
||||
NETWORKS to network.joinToString(",") { it.name },
|
||||
DOMAIN_VERIFICATION to when (domainVerification) {
|
||||
SAFE -> "Verified"
|
||||
UNSAFE -> "Risky"
|
||||
FAILED_TO_VERIFY -> "Unknown"
|
||||
},
|
||||
DOMAIN_VERIFICATION to domainVerification.toAnalyticVerificationStatus(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -62,12 +59,14 @@ sealed class WcAnalyticEvents(
|
|||
class DAppConnected(
|
||||
sessionProposal: WcSessionProposal,
|
||||
sessionForApprove: WcSessionApprove,
|
||||
securityStatus: CheckDAppResult,
|
||||
) : WcAnalyticEvents(
|
||||
event = "dApp Connected",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.DAPP_NAME to sessionProposal.dAppMetaData.name,
|
||||
AnalyticsParam.Key.DAPP_URL to sessionProposal.dAppMetaData.url,
|
||||
AnalyticsParam.Key.BLOCKCHAIN to sessionForApprove.network.joinToString(",") { it.name },
|
||||
DOMAIN_VERIFICATION to securityStatus.toAnalyticVerificationStatus(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -105,6 +104,7 @@ sealed class WcAnalyticEvents(
|
|||
rawRequest: WcSdkSessionRequest,
|
||||
network: Network,
|
||||
emulationStatus: EmulationStatus?,
|
||||
securityStatus: CheckDAppResult?,
|
||||
) : WcAnalyticEvents(
|
||||
event = "Signature Request Received",
|
||||
params = mapOf(
|
||||
|
|
@ -113,6 +113,7 @@ sealed class WcAnalyticEvents(
|
|||
AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method,
|
||||
AnalyticsParam.Key.BLOCKCHAIN to network.name,
|
||||
AnalyticsParam.Key.EMULATION_STATUS to emulationStatus?.status,
|
||||
AnalyticsParam.Key.TYPE to securityStatus?.toAnalyticVerificationStatus(),
|
||||
).mapNotNullValues { it.value },
|
||||
) {
|
||||
enum class EmulationStatus(val status: String) {
|
||||
|
|
@ -125,6 +126,7 @@ sealed class WcAnalyticEvents(
|
|||
class SignatureRequestHandled(
|
||||
rawRequest: WcSdkSessionRequest,
|
||||
network: Network,
|
||||
securityStatus: CheckDAppResult,
|
||||
) : WcAnalyticEvents(
|
||||
event = "Signature Request Handled",
|
||||
params = mapOf(
|
||||
|
|
@ -132,6 +134,7 @@ sealed class WcAnalyticEvents(
|
|||
AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url,
|
||||
AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method,
|
||||
AnalyticsParam.Key.BLOCKCHAIN to network.name,
|
||||
AnalyticsParam.Key.TYPE to securityStatus.toAnalyticVerificationStatus(),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -215,18 +218,26 @@ sealed class WcAnalyticEvents(
|
|||
Source.Domain -> "Domain"
|
||||
Source.SmartContract -> "Smart Contract"
|
||||
},
|
||||
AnalyticsParam.Key.TYPE to when (securityStatus) {
|
||||
SAFE -> "Verified"
|
||||
UNSAFE -> "Risky"
|
||||
FAILED_TO_VERIFY -> "Unknown"
|
||||
},
|
||||
|
||||
AnalyticsParam.Key.TYPE to securityStatus.toAnalyticVerificationStatus(),
|
||||
),
|
||||
) {
|
||||
enum class Source { Domain, SmartContract }
|
||||
}
|
||||
|
||||
enum class DAppVerificationStatus(val status: String) {
|
||||
Verified("Verified"),
|
||||
Risky("Risky"),
|
||||
Unknown("Unknown"),
|
||||
}
|
||||
|
||||
fun CheckDAppResult.toAnalyticVerificationStatus(): String = when (this) {
|
||||
SAFE -> DAppVerificationStatus.Verified
|
||||
UNSAFE -> DAppVerificationStatus.Risky
|
||||
FAILED_TO_VERIFY -> DAppVerificationStatus.Unknown
|
||||
}.status
|
||||
|
||||
companion object {
|
||||
|
||||
const val NETWORKS = "Networks"
|
||||
const val DOMAIN_VERIFICATION = "Domain Verification"
|
||||
const val WC_CATEGORY_NAME = "Wallet Connect"
|
||||
|
|
|
|||
|
|
@ -7,21 +7,22 @@ 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.decompose.ui.UiMessageSender
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.scan.isRing
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.UpdateWalletUseCase
|
||||
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
|
|
@ -53,6 +54,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val updateWalletUseCase: UpdateWalletUseCase,
|
||||
private val cardRepository: CardRepository,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
|
|
@ -77,7 +79,8 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
// sets proper artwork state for initial step
|
||||
// (if we start from backup cards, we need to show proper artwork) ([REDACTED_TASK_KEY])
|
||||
when (getInitialStep()) {
|
||||
MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */ }
|
||||
MultiWalletFinalizeUM.Step.Primary -> { /* state is already set */
|
||||
}
|
||||
MultiWalletFinalizeUM.Step.BackupDevice1 -> {
|
||||
onEvent.emit(MultiWalletFinalizeComponent.Event.OneBackupCardAdded)
|
||||
}
|
||||
|
|
@ -249,11 +252,13 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
}
|
||||
?: userWalletCreated
|
||||
|
||||
saveWalletUseCase(
|
||||
userWallet = userWallet.requireColdWallet().copy(
|
||||
scanResponse = scanResponse.updateScanResponseAfterBackup(),
|
||||
),
|
||||
canOverride = true,
|
||||
updateWalletUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
update = {
|
||||
it.requireColdWallet().copy(
|
||||
scanResponse = scanResponse.updateScanResponseAfterBackup(),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
userWallet
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ interface SendComponent : ComposableContentComponent {
|
|||
interface Factory : ComponentFactory<Params, SendComponent>
|
||||
|
||||
interface ModelCallback {
|
||||
fun onConvertToAnotherToken(lastAmount: String)
|
||||
fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ sealed class DestinationUM {
|
|||
val isValidating: Boolean = false,
|
||||
val isInitialized: Boolean = false,
|
||||
val isRedesignEnabled: Boolean = false,
|
||||
val isRecentHidden: Boolean,
|
||||
) : DestinationUM()
|
||||
|
||||
data class Empty(
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
ChooseManagedTokensComponent.ModelCallback {
|
||||
|
||||
private var lastSavedAmount = ""
|
||||
private var isEnterInFiat = false
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String) {
|
||||
override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) {
|
||||
lastSavedAmount = lastAmount
|
||||
isEnterInFiat = isEnterInFiatSelected
|
||||
modelScope.launch {
|
||||
val showSendViaSwapNotification = shouldShowNotificationUseCase(
|
||||
NotificationId.SendViaSwapTokenSelectorNotification.key,
|
||||
|
|
@ -45,12 +47,11 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCloseSwap(lastAmount: String) {
|
||||
override fun onCloseSwap(lastAmount: String, isEnterInFiatSelected: Boolean) {
|
||||
lastSavedAmount = lastAmount
|
||||
isEnterInFiat = isEnterInFiatSelected
|
||||
modelScope.launch {
|
||||
if (lastAmount.isNotBlank()) {
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount)
|
||||
}
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount, isEnterInFiat)
|
||||
router.replaceAll(SendEntryRoute.Send)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,9 +59,7 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
@Suppress("MagicNumber")
|
||||
override fun onResult() {
|
||||
modelScope.launch {
|
||||
if (lastSavedAmount.isNotBlank()) {
|
||||
swapAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount)
|
||||
}
|
||||
swapAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount, isEnterInFiat)
|
||||
// Workaround in order to execute correct exit animation on SendEntryRoute.ChooseToken
|
||||
router.pop()
|
||||
delay(10L)
|
||||
|
|
@ -70,7 +69,7 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
|
||||
override fun onBack() {
|
||||
modelScope.launch {
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount)
|
||||
sendAmountUpdateTrigger.triggerUpdateAmount(lastSavedAmount, isEnterInFiat)
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted
|
||||
|
|
@ -46,6 +47,7 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
private val urlOpener: UrlOpener,
|
||||
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase,
|
||||
private val feeSelectorReloadListener: FeeSelectorReloadListener,
|
||||
private val feeSelectorCheckReloadListener: FeeSelectorCheckReloadListener,
|
||||
private val feeSelectorCheckReloadTrigger: FeeSelectorCheckReloadTrigger,
|
||||
|
|
@ -188,7 +190,10 @@ internal class FeeSelectorModel @Inject constructor(
|
|||
source = SendScreenSource.Fee,
|
||||
),
|
||||
)
|
||||
feeSelectorBottomSheet.activate(Unit)
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
feeSelectorBottomSheet.activate(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnFeeReloadTriggerUpdates() {
|
||||
|
|
|
|||
|
|
@ -303,12 +303,13 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success
|
||||
val isStubComponent = childStack.value.active.instance is StubComponent
|
||||
val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isSending == true
|
||||
|
||||
val isPopSend = isEmptyRoute || isEmptyStack || isSuccess || isStubComponent
|
||||
if (isPopSend) {
|
||||
router.pop()
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
when {
|
||||
isSendingInProgress -> Unit // Do not anything while transaction sending in progress
|
||||
isPopSend -> router.pop()
|
||||
else -> stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ import com.tangem.core.navigation.url.UrlOpener
|
|||
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.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
|
||||
import com.tangem.domain.settings.NeverShowTapHelpUseCase
|
||||
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
||||
|
|
@ -168,6 +168,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
subscribeOnCheckFeeResultUpdates()
|
||||
initialState()
|
||||
subscribeOnBalanceHidden()
|
||||
subscribeOnTapHelpUpdates()
|
||||
}
|
||||
|
||||
fun updateState(state: SendUM) {
|
||||
|
|
@ -232,10 +233,6 @@ internal class SendConfirmModel @Inject constructor(
|
|||
override fun showEditDestination() {
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
categoryName = analyticsCategoryName,
|
||||
|
|
@ -249,10 +246,6 @@ internal class SendConfirmModel @Inject constructor(
|
|||
override fun showEditAmount() {
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
categoryName = analyticsCategoryName,
|
||||
|
|
@ -266,10 +259,6 @@ internal class SendConfirmModel @Inject constructor(
|
|||
override fun showEditFee() {
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
categoryName = analyticsCategoryName,
|
||||
|
|
@ -348,7 +337,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
val confirmUM = uiState.value.confirmUM
|
||||
|
||||
modelScope.launch {
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false }
|
||||
if (confirmUM is ConfirmUM.Empty) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
|
@ -364,6 +353,16 @@ internal class SendConfirmModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnTapHelpUpdates() {
|
||||
isSendTapHelpEnabledUseCase().getOrNull()
|
||||
?.onEach { showTapHelp ->
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM)
|
||||
}
|
||||
}?.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnNotificationsUpdateTrigger() {
|
||||
notificationsUpdateListener.hasErrorFlow
|
||||
.onEach { hasError ->
|
||||
|
|
|
|||
|
|
@ -226,14 +226,14 @@ internal class SendModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String) {
|
||||
override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) {
|
||||
analyticsEventHandler.send(
|
||||
SendAnalyticEvents.ConvertTokenButtonClicked(
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
params.callback?.onConvertToAnotherToken(lastAmount = lastAmount)
|
||||
params.callback?.onConvertToAnotherToken(lastAmount = lastAmount, isEnterInFiatSelected = isEnterInFiatSelected)
|
||||
}
|
||||
|
||||
override fun resetSendNavigation() {
|
||||
|
|
@ -454,7 +454,7 @@ internal class SendModel @Inject constructor(
|
|||
)
|
||||
// If it is in active state use flow to update value in amount component
|
||||
modelScope.launch {
|
||||
amount?.let { sendAmountUpdateTrigger.triggerUpdateAmount(it) }
|
||||
amount?.let { sendAmountUpdateTrigger.triggerUpdateAmount(it, null) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -215,11 +215,13 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
val isEmptyRoute = childStack.value.active.configuration == CommonSendRoute.Empty
|
||||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success
|
||||
val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isSending == true
|
||||
|
||||
if (isEmptyRoute || isEmptyStack || isSuccess) {
|
||||
router.pop()
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
val isPopSend = isEmptyRoute || isEmptyStack || isSuccess
|
||||
when {
|
||||
isSendingInProgress -> Unit // Do not anything while transaction sending in progress
|
||||
isPopSend -> router.pop()
|
||||
else -> stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
configConfirmNavigation()
|
||||
subscribeOnNotificationsUpdateTrigger()
|
||||
subscribeOnCheckFeeResultUpdates()
|
||||
subscribeOnTapHelpUpdates()
|
||||
initialState()
|
||||
}
|
||||
|
||||
|
|
@ -176,10 +177,6 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
override fun showEditDestination() {
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
categoryName = analyticsCategoryName,
|
||||
|
|
@ -193,10 +190,6 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
override fun showEditFee() {
|
||||
modelScope.launch {
|
||||
neverShowTapHelpUseCase()
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = false) ?: it.confirmUM)
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
CommonSendAnalyticEvents.ScreenReopened(
|
||||
categoryName = analyticsCategoryName,
|
||||
|
|
@ -262,7 +255,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
modelScope.launch {
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false }
|
||||
if (confirmUM is ConfirmUM.Empty || isEmptyFee) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
|
|
@ -277,6 +270,16 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnTapHelpUpdates() {
|
||||
isSendTapHelpEnabledUseCase().getOrNull()
|
||||
?.onEach { showTapHelp ->
|
||||
_uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM)
|
||||
}
|
||||
}?.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnNotificationsUpdateTrigger() {
|
||||
notificationsUpdateListener.hasErrorFlow
|
||||
.onEach { hasError ->
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ internal class SendAmountComponent(
|
|||
|
||||
interface ModelCallback : NavigationModelCallback {
|
||||
fun onAmountResult(amountUM: AmountState, isResetPredefined: Boolean)
|
||||
fun onConvertToAnotherToken(lastAmount: String)
|
||||
fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean)
|
||||
fun resetSendNavigation()
|
||||
fun onError(error: GetUserWalletError)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ interface SendAmountReduceListener {
|
|||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SendAmountUpdateTrigger {
|
||||
suspend fun triggerUpdateAmount(amountValue: String)
|
||||
suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -38,7 +38,7 @@ interface SendAmountUpdateTrigger {
|
|||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SendAmountUpdateListener {
|
||||
val updateAmountTriggerFlow: Flow<String>
|
||||
val updateAmountTriggerFlow: Flow<Pair<String, Boolean?>>
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
|
@ -51,7 +51,7 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() :
|
|||
override val reduceToTriggerFlow = MutableSharedFlow<BigDecimal>()
|
||||
override val reduceByTriggerFlow = MutableSharedFlow<ReduceByData>()
|
||||
override val ignoreReduceTriggerFlow = MutableSharedFlow<Unit>()
|
||||
override val updateAmountTriggerFlow = MutableSharedFlow<String>()
|
||||
override val updateAmountTriggerFlow = MutableSharedFlow<Pair<String, Boolean?>>()
|
||||
|
||||
override suspend fun triggerReduceBy(reduceBy: ReduceByData) {
|
||||
reduceByTriggerFlow.emit(reduceBy)
|
||||
|
|
@ -65,7 +65,7 @@ internal class DefaultSendAmountReduceTrigger @Inject constructor() :
|
|||
ignoreReduceTriggerFlow.emit(Unit)
|
||||
}
|
||||
|
||||
override suspend fun triggerUpdateAmount(amountValue: String) {
|
||||
updateAmountTriggerFlow.emit(amountValue)
|
||||
override suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean?) {
|
||||
updateAmountTriggerFlow.emit(amountValue to isEnterInFiatSelected)
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +290,15 @@ internal class SendAmountModel @Inject constructor(
|
|||
isPrimaryButtonEnabled = false,
|
||||
) ?: it
|
||||
}
|
||||
amountParams.callback.onConvertToAnotherToken(amountFieldData?.amountTextField?.value.orEmpty())
|
||||
|
||||
val isEnterInFiatSelected = amountFieldData?.amountTextField?.isFiatValue == true
|
||||
val lastAmount = if (isEnterInFiatSelected) {
|
||||
amountFieldData.amountTextField.fiatValue
|
||||
} else {
|
||||
amountFieldData?.amountTextField?.value
|
||||
}
|
||||
|
||||
amountParams.callback.onConvertToAnotherToken(lastAmount.orEmpty(), isEnterInFiatSelected)
|
||||
}
|
||||
|
||||
private fun subscribeOnAmountReduceToTriggerUpdates() {
|
||||
|
|
@ -346,7 +354,8 @@ internal class SendAmountModel @Inject constructor(
|
|||
|
||||
private fun subscribeOnAmountUpdateTriggerUpdates() {
|
||||
sendAmountUpdateListener.updateAmountTriggerFlow
|
||||
.onEach { amount ->
|
||||
.onEach { (amount, isEnterInFiat) ->
|
||||
isEnterInFiat?.let { onCurrencyChangeClick(isEnterInFiat) }
|
||||
onAmountValueChange(amount)
|
||||
saveResult()
|
||||
}.launchIn(modelScope)
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ internal class SendDestinationInitialStateTransformer(
|
|||
networkName = cryptoCurrency.network.name,
|
||||
isValidating = false,
|
||||
isRedesignEnabled = isRedesignEnabled,
|
||||
isRecentHidden = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ package com.tangem.features.send.v2.subcomponents.destination.model.transformers
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientHistoryListConverter
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientWalletListConverter
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationWalletUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal class SendDestinationValidationResultTransformer(
|
|||
wallets = state.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = !isValidAddress && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
isRecentHidden = isValidAddress,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider<Destina
|
|||
isValidating = false,
|
||||
isInitialized = true,
|
||||
isRedesignEnabled = false,
|
||||
isRecentHidden = false,
|
||||
)
|
||||
|
||||
override val values: Sequence<DestinationUM.Content>
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ import com.tangem.core.ui.components.containers.FooterContainer
|
|||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationClickIntents
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val ADDRESS_FIELD_KEY = "ADDRESS_FIELD_KEY"
|
||||
|
|
@ -74,13 +74,14 @@ internal fun SendDestinationContent(
|
|||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recipient_wallets_title,
|
||||
isVisible = wallets.isNotEmpty() && wallets.first().isVisible,
|
||||
isVisible = wallets.isNotEmpty() && wallets.first().isVisible && !state.isRecentHidden,
|
||||
isFirst = true,
|
||||
)
|
||||
listItem(
|
||||
list = wallets,
|
||||
isLast = recipients.any { !it.isVisible },
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isRecentHidden = state.isRecentHidden,
|
||||
onClick = { title ->
|
||||
clickIntents.onRecipientAddressValueChange(
|
||||
title,
|
||||
|
|
@ -90,13 +91,14 @@ internal fun SendDestinationContent(
|
|||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recent_transactions,
|
||||
isVisible = recipients.isNotEmpty() && recipients.first().isVisible,
|
||||
isVisible = recipients.isNotEmpty() && recipients.first().isVisible && !state.isRecentHidden,
|
||||
isFirst = wallets.any { !it.isVisible },
|
||||
)
|
||||
listItem(
|
||||
list = recipients,
|
||||
isLast = true,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isRecentHidden = state.isRecentHidden,
|
||||
onClick = { title ->
|
||||
clickIntents.onRecipientAddressValueChange(
|
||||
title,
|
||||
|
|
@ -257,6 +259,7 @@ private fun LazyListScope.listItem(
|
|||
list: ImmutableList<DestinationRecipientListUM>,
|
||||
isLast: Boolean,
|
||||
isBalanceHidden: Boolean,
|
||||
isRecentHidden: Boolean,
|
||||
onClick: (String) -> Unit,
|
||||
) {
|
||||
items(
|
||||
|
|
@ -267,7 +270,7 @@ private fun LazyListScope.listItem(
|
|||
val item = list[index]
|
||||
val title = item.title.resolveReference()
|
||||
|
||||
if (item.isVisible) {
|
||||
if (item.isVisible && !isRecentHidden) {
|
||||
ListItemWithIcon(
|
||||
title = title,
|
||||
subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ interface SendWithSwapComponent : ComposableContentComponent {
|
|||
interface Factory : ComponentFactory<Params, SendWithSwapComponent>
|
||||
|
||||
interface ModelCallback {
|
||||
fun onCloseSwap(lastAmount: String)
|
||||
fun onCloseSwap(lastAmount: String, isEnterInFiatSelected: Boolean)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ package com.tangem.features.swap.v2.api.subcomponents
|
|||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SwapAmountUpdateTrigger {
|
||||
suspend fun triggerUpdateAmount(amountValue: String)
|
||||
suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean)
|
||||
|
||||
suspend fun triggerQuoteReload()
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ import javax.inject.Singleton
|
|||
* Different from another triggers because it takes raw string instead of BigDecimal
|
||||
*/
|
||||
interface SwapAmountUpdateListener {
|
||||
val updateAmountTriggerFlow: Flow<String>
|
||||
val updateAmountTriggerFlow: Flow<Pair<String, Boolean>>
|
||||
|
||||
val reloadQuotesTriggerFlow: Flow<Unit>
|
||||
}
|
||||
|
|
@ -44,8 +44,8 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() :
|
|||
SwapAmountReduceTrigger,
|
||||
SwapAmountReduceListener {
|
||||
|
||||
override val updateAmountTriggerFlow: SharedFlow<String>
|
||||
field = MutableSharedFlow<String>()
|
||||
override val updateAmountTriggerFlow: SharedFlow<Pair<String, Boolean>>
|
||||
field = MutableSharedFlow<Pair<String, Boolean>>()
|
||||
|
||||
override val reduceToTriggerFlow: SharedFlow<BigDecimal>
|
||||
field = MutableSharedFlow<BigDecimal>()
|
||||
|
|
@ -59,8 +59,8 @@ internal class DefaultSwapAmountUpdateTrigger @Inject constructor() :
|
|||
override val reloadQuotesTriggerFlow: Flow<Unit>
|
||||
field = MutableSharedFlow<Unit>()
|
||||
|
||||
override suspend fun triggerUpdateAmount(amountValue: String) {
|
||||
updateAmountTriggerFlow.emit(amountValue)
|
||||
override suspend fun triggerUpdateAmount(amountValue: String, isEnterInFiatSelected: Boolean) {
|
||||
updateAmountTriggerFlow.emit(amountValue to isEnterInFiatSelected)
|
||||
}
|
||||
|
||||
override suspend fun triggerQuoteReload() {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ internal class SwapAmountComponent @AssistedInject constructor(
|
|||
|
||||
interface ModelCallback : NavigationModelCallback {
|
||||
fun onAmountResult(amountUM: SwapAmountUM)
|
||||
fun onSeparatorClick(lastAmount: String)
|
||||
fun onSeparatorClick(lastAmount: String, isEnterInFiatSelected: Boolean)
|
||||
fun resetSendWithSwapNavigation(resetNavigation: Boolean)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ internal sealed class SwapAmountComponentParams {
|
|||
override val filterProviderTypes: List<ExpressProviderType> = emptyList(),
|
||||
val title: TextReference,
|
||||
val callback: SwapAmountComponent.ModelCallback,
|
||||
val currentRoute: Flow<SwapAmountRoute>,
|
||||
val currentRoute: Flow<SendWithSwapRoute>,
|
||||
) : SwapAmountComponentParams()
|
||||
|
||||
data class AmountBlockParams(
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import com.tangem.features.swap.v2.impl.amount.model.transformers.*
|
|||
import com.tangem.features.swap.v2.impl.chooseprovider.SwapChooseProviderComponent
|
||||
import com.tangem.features.swap.v2.impl.common.SwapAlertFactory
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
|
|
@ -347,7 +348,10 @@ internal class SwapAmountModel @Inject constructor(
|
|||
|
||||
private fun confirmSendWithSwapClose() {
|
||||
val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data
|
||||
val amountField = uiState.value.swapDirection.withSwapDirection(
|
||||
onDirect = { uiState.value.primaryAmount },
|
||||
onReverse = { uiState.value.secondaryAmount },
|
||||
).amountField as? AmountState.Data
|
||||
|
||||
val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
|
||||
if (primaryCryptoCurrencyStatus != null) {
|
||||
|
|
@ -366,7 +370,16 @@ internal class SwapAmountModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
amountParams.callback.onSeparatorClick(lastAmount = amountFieldData?.amountTextField?.value.orEmpty())
|
||||
val isEnterInFiatSelected = amountField?.amountTextField?.isFiatValue == true
|
||||
val lastAmount = if (isEnterInFiatSelected) {
|
||||
amountField.amountTextField.fiatValue
|
||||
} else {
|
||||
amountField?.amountTextField?.value
|
||||
}
|
||||
amountParams.callback.onSeparatorClick(
|
||||
lastAmount = lastAmount.orEmpty(),
|
||||
isEnterInFiatSelected = isEnterInFiatSelected,
|
||||
)
|
||||
}
|
||||
|
||||
private fun subscribeOnCryptoCurrencyStatusFlow() {
|
||||
|
|
@ -406,9 +419,10 @@ internal class SwapAmountModel @Inject constructor(
|
|||
|
||||
private fun subscribeOnAmountUpdateTriggerUpdates() {
|
||||
swapAmountUpdateListener.updateAmountTriggerFlow
|
||||
.onEach {
|
||||
.onEach { (amount, isEnterInFiat) ->
|
||||
if (uiState.value is SwapAmountUM.Content) {
|
||||
onAmountValueChange(it)
|
||||
onCurrencyChangeClick(isEnterInFiat)
|
||||
onAmountValueChange(amount)
|
||||
saveResult()
|
||||
}
|
||||
}
|
||||
|
|
@ -689,7 +703,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
flow = uiState,
|
||||
flow2 = params.currentRoute,
|
||||
transform = { state, route -> state to route },
|
||||
).onEach { (state, route) ->
|
||||
).filter { (_, route) -> route is SendWithSwapRoute.Amount }.onEach { (state, route) ->
|
||||
params.callback.onNavigationResult(
|
||||
NavigationUM.Content(
|
||||
title = resourceReference(R.string.common_amount),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.utils.transformer.Transformer
|
|||
|
||||
internal object SwapAmountPasteTransformer : Transformer<SwapAmountUM> {
|
||||
override fun transform(prevState: SwapAmountUM): SwapAmountUM {
|
||||
return prevState.updateAmount(
|
||||
val updatedState = prevState.updateAmount(
|
||||
onPrimaryAmount = {
|
||||
copy(
|
||||
amountField = AmountPastedTriggerDismissTransformer.transform(
|
||||
|
|
@ -23,5 +23,9 @@ internal object SwapAmountPasteTransformer : Transformer<SwapAmountUM> {
|
|||
)
|
||||
},
|
||||
)
|
||||
|
||||
return (updatedState as? SwapAmountUM.Content)?.copy(
|
||||
isPrimaryButtonEnabled = prevState.isPrimaryButtonEnabled,
|
||||
) ?: updatedState
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import com.tangem.features.swap.v2.impl.amount.SwapAmountComponent
|
|||
import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM
|
||||
import com.tangem.features.swap.v2.impl.common.SwapUtils.SEND_WITH_SWAP_PROVIDER_TYPES
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.model.SendWithSwapModel
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.success.SendWithSwapSuccessComponent
|
||||
|
|
@ -136,7 +137,7 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
params = SwapAmountComponentParams.AmountParams(
|
||||
amountUM = model.uiState.value.amountUM,
|
||||
title = resourceReference(R.string.common_send),
|
||||
currentRoute = model.currentRoute.filterIsInstance<SendWithSwapRoute.Amount>(),
|
||||
currentRoute = model.currentRoute,
|
||||
isBalanceHidingFlow = model.isBalanceHiddenFlow,
|
||||
analyticsCategoryName = model.analyticCategoryName,
|
||||
primaryCryptoCurrencyStatusFlow = model.primaryCryptoCurrencyStatusFlow,
|
||||
|
|
@ -202,11 +203,15 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor(
|
|||
|
||||
private fun onChildBack() {
|
||||
val isEmptyStack = childStack.value.backStack.isEmpty()
|
||||
val isSuccess = model.uiState.value.confirmUM is ConfirmUM.Success
|
||||
val isSendingInProgress = (model.uiState.value.confirmUM as? ConfirmUM.Content)?.isTransactionInProcess == true
|
||||
|
||||
if (isEmptyStack) {
|
||||
router.pop()
|
||||
} else {
|
||||
stackNavigation.pop()
|
||||
val isPopSend = isEmptyStack || isSuccess
|
||||
|
||||
when {
|
||||
isSendingInProgress -> Unit // Do not anything while transaction sending in progress
|
||||
isPopSend -> router.pop()
|
||||
else -> stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -51,6 +52,8 @@ import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
|||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.SendWithSwapConfirmComponent
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmInitialStateTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmSendingStateTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmSentStateTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers.SendWithSwapConfirmationNotificationsTransformer
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import com.tangem.lib.crypto.BlockchainFeeUtils.patchTransactionFeeForSwap
|
||||
|
|
@ -141,6 +144,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
configConfirmNavigation()
|
||||
initialState()
|
||||
subscribeOnNotificationUpdates()
|
||||
subscribeOnTapHelpUpdates()
|
||||
}
|
||||
|
||||
override fun onFeeResult(feeSelectorUM: FeeSelectorUM) {
|
||||
|
|
@ -239,10 +243,12 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
private fun onSendClick() {
|
||||
val provider = confirmData.quote?.provider ?: return
|
||||
modelScope.launch {
|
||||
uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(true))
|
||||
swapTransactionSender.sendTransaction(
|
||||
confirmData = confirmData,
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
onExpressError = { expressError ->
|
||||
uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false))
|
||||
swapAlertFactory.getGenericErrorState(
|
||||
expressError = expressError,
|
||||
onFailedTxEmailClick = {
|
||||
|
|
@ -259,6 +265,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
)
|
||||
},
|
||||
onSendError = { error ->
|
||||
uiState.transformerUpdate(SendWithSwapConfirmSendingStateTransformer(false))
|
||||
swapAlertFactory.getSendTransactionErrorState(
|
||||
error = error,
|
||||
onFailedTxEmailClick = {
|
||||
|
|
@ -280,19 +287,17 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
networkId = primaryCurrencyStatus.currency.network.id,
|
||||
).getOrNull().orEmpty()
|
||||
sendSuccessAnalytics()
|
||||
uiState.update {
|
||||
it.copy(
|
||||
confirmUM = ConfirmUM.Success(
|
||||
isPrimaryButtonEnabled = true,
|
||||
transactionDate = timestamp,
|
||||
txUrl = txUrl,
|
||||
provider = provider,
|
||||
swapDataModel = data,
|
||||
),
|
||||
)
|
||||
}
|
||||
uiState.transformerUpdate(
|
||||
SendWithSwapConfirmSentStateTransformer(
|
||||
timestamp = timestamp,
|
||||
txUrl = txUrl,
|
||||
provider = provider,
|
||||
swapDataModel = data,
|
||||
),
|
||||
)
|
||||
router.replaceAll(SendWithSwapRoute.Success)
|
||||
},
|
||||
expressOperationType = ExpressOperationType.SEND_WITH_SWAP,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -311,7 +316,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
val confirmUM = uiState.value.confirmUM
|
||||
|
||||
modelScope.launch {
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase().getOrElse { false }
|
||||
val isShowTapHelp = isSendTapHelpEnabledUseCase.invokeSync().getOrElse { false }
|
||||
if (confirmUM is ConfirmUM.Empty) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
|
|
@ -325,6 +330,16 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnTapHelpUpdates() {
|
||||
isSendTapHelpEnabledUseCase().getOrNull()
|
||||
?.onEach { showTapHelp ->
|
||||
uiState.update {
|
||||
val confirmUM = it.confirmUM as? ConfirmUM.Content
|
||||
it.copy(confirmUM = confirmUM?.copy(showTapHelp = showTapHelp) ?: it.confirmUM)
|
||||
}
|
||||
}?.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateConfirmNotifications() {
|
||||
modelScope.launch {
|
||||
sendNotificationsUpdateTrigger.triggerUpdate(
|
||||
|
|
@ -415,7 +430,15 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
backIconRes = R.drawable.ic_back_24,
|
||||
backIconClick = router::pop,
|
||||
primaryButton = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_send),
|
||||
textReference = when (confirmUM) {
|
||||
is ConfirmUM.Success -> resourceReference(R.string.common_close)
|
||||
is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) {
|
||||
resourceReference(R.string.send_sending)
|
||||
} else {
|
||||
resourceReference(R.string.common_send)
|
||||
}
|
||||
else -> resourceReference(R.string.common_send)
|
||||
},
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isIconVisible = isReadyToSend,
|
||||
isHapticClick = isReadyToSend,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -40,6 +41,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
onSendSuccess: (String, Long, SwapDataModel) -> Unit,
|
||||
onExpressError: (ExpressError) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
expressOperationType: ExpressOperationType,
|
||||
) {
|
||||
val provider = confirmData.quote?.provider ?: return
|
||||
|
||||
|
|
@ -50,6 +52,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
onExpressError = onExpressError,
|
||||
onSendSuccess = onSendSuccess,
|
||||
onSendError = onSendError,
|
||||
expressOperationType = expressOperationType,
|
||||
)
|
||||
ExpressProviderType.DEX,
|
||||
ExpressProviderType.DEX_BRIDGE,
|
||||
|
|
@ -67,6 +70,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
onSendSuccess: (String, Long, SwapDataModel) -> Unit,
|
||||
onExpressError: (ExpressError) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
expressOperationType: ExpressOperationType,
|
||||
) {
|
||||
val fromStatus = confirmData.fromCryptoCurrencyStatus ?: return
|
||||
val toStatus = confirmData.toCryptoCurrencyStatus ?: return
|
||||
|
|
@ -92,6 +96,7 @@ internal class SwapTransactionSender @AssistedInject constructor(
|
|||
toAddress = destination,
|
||||
expressProvider = provider,
|
||||
rateType = rateType,
|
||||
expressOperationType,
|
||||
).getOrElse { onExpressError(it); return }
|
||||
|
||||
createAndSendCexTransaction(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
|
||||
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SendWithSwapConfirmSendingStateTransformer(
|
||||
private val isTransactionInProcess: Boolean,
|
||||
) : Transformer<SendWithSwapUM> {
|
||||
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
|
||||
val confirmUM = prevState.confirmUM as? ConfirmUM.Content ?: return prevState
|
||||
return prevState.copy(
|
||||
confirmUM = confirmUM.copy(
|
||||
isPrimaryButtonEnabled = !isTransactionInProcess,
|
||||
isTransactionInProcess = isTransactionInProcess,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.confirm.model.transformers
|
||||
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.swap.models.SwapDataModel
|
||||
import com.tangem.features.swap.v2.impl.common.entity.ConfirmUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.entity.SendWithSwapUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SendWithSwapConfirmSentStateTransformer(
|
||||
private val txUrl: String,
|
||||
private val timestamp: Long,
|
||||
private val provider: ExpressProvider,
|
||||
private val swapDataModel: SwapDataModel,
|
||||
) : Transformer<SendWithSwapUM> {
|
||||
override fun transform(prevState: SendWithSwapUM): SendWithSwapUM {
|
||||
return prevState.copy(
|
||||
confirmUM = ConfirmUM.Success(
|
||||
isPrimaryButtonEnabled = true,
|
||||
transactionDate = timestamp,
|
||||
txUrl = txUrl,
|
||||
provider = provider,
|
||||
swapDataModel = swapDataModel,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -110,8 +110,8 @@ internal class SendWithSwapModel @Inject constructor(
|
|||
uiState.update { it.copy(navigationUM = navigationUM) }
|
||||
}
|
||||
|
||||
override fun onSeparatorClick(lastAmount: String) {
|
||||
params.callback?.onCloseSwap(lastAmount)
|
||||
override fun onSeparatorClick(lastAmount: String, isEnterInFiatSelected: Boolean) {
|
||||
params.callback?.onCloseSwap(lastAmount, isEnterInFiatSelected)
|
||||
router.popTo(initialRoute)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ private fun SendWithSwapSuccessContent_Preview() {
|
|||
isValidating = false,
|
||||
isInitialized = false,
|
||||
isRedesignEnabled = false,
|
||||
isRecentHidden = false,
|
||||
),
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = TransactionFee.Single(
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
/** Data */
|
||||
implementation(projects.data.common)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -283,6 +284,7 @@ internal class DefaultSwapRepository(
|
|||
providerId: String,
|
||||
rateType: RateType,
|
||||
toAddress: String,
|
||||
expressOperationType: ExpressOperationType,
|
||||
refundAddress: String?, // for cex only
|
||||
refundExtraId: String?, // for cex only
|
||||
): Either<ExpressDataError, SwapDataModel> {
|
||||
|
|
@ -304,6 +306,7 @@ internal class DefaultSwapRepository(
|
|||
requestId = requestId,
|
||||
refundAddress = refundAddress,
|
||||
refundExtraId = refundExtraId,
|
||||
partnerOperationType = expressOperationType.value,
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ dependencies {
|
|||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.domain.api
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -64,6 +65,7 @@ interface SwapRepository {
|
|||
providerId: String,
|
||||
rateType: RateType,
|
||||
toAddress: String,
|
||||
expressOperationType: ExpressOperationType,
|
||||
refundAddress: String? = null, // for cex only
|
||||
refundExtraId: String? = null, // for cex only
|
||||
): Either<ExpressDataError, SwapDataModel>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ dependencies {
|
|||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -65,6 +66,7 @@ interface SwapInteractor {
|
|||
amountToSwap: String,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
fee: TxFee,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapTransactionState
|
||||
|
||||
// suspend fun updateQuotesStateWithSelectedFee(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
|
|||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -287,6 +288,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
selectedFee = selectedFee,
|
||||
amount = amount,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
expressOperationType = ExpressOperationType.SWAP,
|
||||
)
|
||||
}
|
||||
ExchangeProviderType.CEX -> {
|
||||
|
|
@ -313,6 +315,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
selectedFee: FeeType,
|
||||
amount: SwapAmount,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): Pair<SwapProvider, SwapState> {
|
||||
val maybeQuotes = repository.findBestQuote(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -349,6 +352,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
toToken = toToken,
|
||||
amount = amount,
|
||||
selectedFee = selectedFee,
|
||||
expressOperationType = expressOperationType,
|
||||
)
|
||||
} else {
|
||||
provider to getQuotesState(
|
||||
|
|
@ -495,6 +499,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amountToSwap: String,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
fee: TxFee,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapTransactionState {
|
||||
Timber.i(
|
||||
"""
|
||||
|
|
@ -529,6 +534,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount = amountToSwapWithFee,
|
||||
txFee = fee,
|
||||
swapProvider = swapProvider,
|
||||
expressOperationType = expressOperationType,
|
||||
)
|
||||
}
|
||||
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
|
||||
|
|
@ -676,6 +682,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount: SwapAmount,
|
||||
txFee: TxFee,
|
||||
swapProvider: SwapProvider,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapTransactionState {
|
||||
val exchangeData = repository.getExchangeData(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -689,9 +696,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
toDecimals = currencyToGet.currency.decimals,
|
||||
providerId = swapProvider.providerId,
|
||||
rateType = RateType.FLOAT,
|
||||
expressOperationType = expressOperationType,
|
||||
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value,
|
||||
refundExtraId = null, // currently always null
|
||||
refundExtraId = null, // currently always null,
|
||||
).getOrElse { return SwapTransactionState.Error.ExpressError(it) }
|
||||
|
||||
val exchangeDataCex =
|
||||
|
|
@ -1161,6 +1169,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
toToken: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
selectedFee: FeeType,
|
||||
expressOperationType: ExpressOperationType,
|
||||
): SwapState {
|
||||
return repository.getExchangeData(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -1176,6 +1185,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
rateType = RateType.FLOAT,
|
||||
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
refundAddress = fromToken.value.networkAddress?.defaultAddress?.value,
|
||||
expressOperationType = expressOperationType,
|
||||
).fold(
|
||||
ifRight = { swapData ->
|
||||
val transaction = swapData.transaction as ExpressTransactionModel.DEX
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ dependencies {
|
|||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.utils.InputNumberFormatter
|
|||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
|
|
@ -612,6 +613,7 @@ internal class SwapModel @Inject constructor(
|
|||
amountToSwap = requireNotNull(dataState.amount),
|
||||
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
|
||||
fee = fee,
|
||||
expressOperationType = ExpressOperationType.SWAP,
|
||||
)
|
||||
}.onSuccess {
|
||||
when (it) {
|
||||
|
|
|
|||
|
|
@ -78,10 +78,11 @@ internal class DefaultTokenReceiveComponent @AssistedInject constructor(
|
|||
appComponentContext = appComponentContext,
|
||||
params = TokenReceiveQrCodeComponent.TokenReceiveQrCodeParams(
|
||||
cryptoCurrency = model.params.config.cryptoCurrency,
|
||||
address = model.state.value.addresses[config.addressId] ?: error("Address has to be there"),
|
||||
address = model.state.value.addresses.find { it.value == config.address } ?: error(
|
||||
"Address has to be there",
|
||||
),
|
||||
callback = model,
|
||||
onDismiss = ::dismiss,
|
||||
id = config.addressId,
|
||||
),
|
||||
)
|
||||
TokenReceiveRoutes.ReceiveAssets -> TokenReceiveAssetsComponent(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
|||
import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel
|
||||
import com.tangem.features.tokenreceive.ui.TokenReceiveAssetsContent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
internal class TokenReceiveAssetsComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
|
|
@ -29,13 +28,13 @@ internal class TokenReceiveAssetsComponent(
|
|||
}
|
||||
|
||||
internal interface TokenReceiveAssetsModelCallback {
|
||||
fun onQrCodeClick(id: Int)
|
||||
fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource)
|
||||
fun onQrCodeClick(address: String)
|
||||
fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource)
|
||||
}
|
||||
|
||||
data class TokenReceiveAssetsParams(
|
||||
val notificationConfigs: ImmutableList<NotificationUM>,
|
||||
val addresses: ImmutableMap<Int, ReceiveAddress>,
|
||||
val addresses: ImmutableList<ReceiveAddress>,
|
||||
val callback: TokenReceiveAssetsModelCallback,
|
||||
val onDismiss: () -> Unit,
|
||||
val showMemoDisclaimer: Boolean,
|
||||
|
|
|
|||
|
|
@ -27,12 +27,11 @@ internal class TokenReceiveQrCodeComponent(
|
|||
}
|
||||
|
||||
internal interface TokenReceiveQrCodeModelCallback {
|
||||
fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource)
|
||||
fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource)
|
||||
fun onShareClick(address: String)
|
||||
}
|
||||
|
||||
data class TokenReceiveQrCodeParams(
|
||||
val id: Int,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val address: ReceiveAddress,
|
||||
val callback: TokenReceiveQrCodeModelCallback,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
package com.tangem.features.tokenreceive.entity
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.R
|
||||
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.wrappedList
|
||||
import com.tangem.domain.models.ReceiveAddressModel
|
||||
import com.tangem.domain.models.TokenReceiveNotification
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.ens.EnsAddress
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Ens
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress.Type.Primary
|
||||
import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class TokenReceiveStateFactory(
|
||||
private val currentStateProvider: Provider<TokenReceiveUM>,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val addresses: List<ReceiveAddressModel>,
|
||||
private val tokenReceiveNotification: List<TokenReceiveNotification>,
|
||||
) {
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
fun getInitialState(tokenName: String): TokenReceiveUM {
|
||||
return TokenReceiveUM(
|
||||
addresses = mapAddresses(
|
||||
addresses = addresses,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
iconState = iconStateConverter.convert(cryptoCurrency),
|
||||
network = cryptoCurrency.network.name,
|
||||
isEnsResultLoading = false,
|
||||
notificationConfigs = getNotifications(
|
||||
tokenName = tokenName,
|
||||
tokenReceiveNotification = tokenReceiveNotification,
|
||||
).toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun getLoadingState(): TokenReceiveUM {
|
||||
return currentStateProvider.invoke().copy(isEnsResultLoading = true)
|
||||
}
|
||||
|
||||
fun getLoadedState(ensAddress: List<EnsAddress>): TokenReceiveUM {
|
||||
val currentAddressValues = currentStateProvider.invoke().addresses.map { it.value }.toSet()
|
||||
|
||||
val newEnsAddresses = ensAddress
|
||||
.filterIsInstance<EnsAddress.Address>()
|
||||
.filterNot { it.name in currentAddressValues }
|
||||
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) }
|
||||
|
||||
val combinedAddresses = (currentStateProvider.invoke().addresses + newEnsAddresses)
|
||||
.sortedWith(
|
||||
compareBy { address ->
|
||||
when (address.type) {
|
||||
is Ens -> 0
|
||||
is Primary.Default -> 1
|
||||
is Primary.Legacy -> 2
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return currentStateProvider.invoke().copy(
|
||||
isEnsResultLoading = false,
|
||||
addresses = combinedAddresses.toPersistentList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapAddresses(
|
||||
addresses: List<ReceiveAddressModel>,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): ImmutableList<ReceiveAddress> {
|
||||
val needUseToLegacyAndDefaultName = addresses.any { it.nameService == ReceiveAddressModel.NameService.Legacy }
|
||||
|
||||
val receiveAddresses = addresses.map { model ->
|
||||
val type = when (model.nameService) {
|
||||
ReceiveAddressModel.NameService.Default -> {
|
||||
val displayName = when (cryptoCurrency) {
|
||||
is CryptoCurrency.Coin -> cryptoCurrency.name
|
||||
is CryptoCurrency.Token -> cryptoCurrency.symbol
|
||||
}
|
||||
|
||||
Primary.Default(
|
||||
displayName = if (needUseToLegacyAndDefaultName) {
|
||||
TextReference.Res(R.string.domain_receive_assets_default_address)
|
||||
} else {
|
||||
TextReference.Combined(
|
||||
wrappedList(
|
||||
TextReference.Str(displayName),
|
||||
TextReference.Str(" "),
|
||||
TextReference.Res(R.string.common_address),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
ReceiveAddressModel.NameService.Ens -> Ens
|
||||
ReceiveAddressModel.NameService.Legacy -> Primary.Legacy(
|
||||
displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address),
|
||||
)
|
||||
}
|
||||
ReceiveAddress(
|
||||
value = model.value,
|
||||
type = type,
|
||||
)
|
||||
}
|
||||
|
||||
val sortedAddresses = receiveAddresses.sortedWith(
|
||||
compareBy { address ->
|
||||
when (address.type) {
|
||||
is Ens -> 0
|
||||
is Primary.Default -> 1
|
||||
is Primary.Legacy -> 2
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return sortedAddresses.toPersistentList()
|
||||
}
|
||||
|
||||
private fun getNotifications(
|
||||
tokenName: String,
|
||||
tokenReceiveNotification: List<TokenReceiveNotification>,
|
||||
): List<NotificationUM> {
|
||||
return buildList {
|
||||
add(
|
||||
NotificationUM.Info(
|
||||
title = resourceReference(
|
||||
R.string.receive_bottom_sheet_warning_title,
|
||||
wrappedList(
|
||||
tokenName,
|
||||
cryptoCurrency.network.name,
|
||||
),
|
||||
),
|
||||
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
|
||||
iconTint = NotificationConfig.IconTint.Accent,
|
||||
),
|
||||
)
|
||||
tokenReceiveNotification.map { notification ->
|
||||
add(
|
||||
NotificationUM.Warning(
|
||||
title = resourceReference(notification.title),
|
||||
subtitle = resourceReference(notification.subtitle),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ internal class TokenReceiveAssetsModel @Inject constructor(
|
|||
ReceiveAssetsUM(
|
||||
onCopyClick = {
|
||||
params.callback.onCopyClick(
|
||||
id = it,
|
||||
address = it,
|
||||
source = TokenReceiveCopyActionSource.Receive,
|
||||
)
|
||||
},
|
||||
|
|
@ -54,7 +54,7 @@ internal class TokenReceiveAssetsModel @Inject constructor(
|
|||
)
|
||||
|
||||
private fun configureEnsStatus(): AnalyticsParam.EnsStatus {
|
||||
val hasEnsAddress = params.addresses.values.any { it.type == ReceiveAddress.Type.Ens }
|
||||
val hasEnsAddress = params.addresses.any { it.type == ReceiveAddress.Type.Ens }
|
||||
return if (hasEnsAddress) {
|
||||
AnalyticsParam.EnsStatus.FULL
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,22 +2,13 @@ package com.tangem.features.tokenreceive.model
|
|||
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.push
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
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.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
|
||||
|
|
@ -28,12 +19,11 @@ 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.entity.TokenReceiveStateFactory
|
||||
import com.tangem.features.tokenreceive.route.TokenReceiveRoutes
|
||||
import com.tangem.features.tokenreceive.ui.state.TokenReceiveUM
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentMap
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -52,14 +42,21 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : Model(), TokenReceiveModelCallback {
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
private val tokenReceiveStateFactory: TokenReceiveStateFactory by lazy {
|
||||
TokenReceiveStateFactory(
|
||||
cryptoCurrency = params.config.cryptoCurrency,
|
||||
addresses = params.config.receiveAddress,
|
||||
tokenReceiveNotification = params.config.tokenReceiveNotification,
|
||||
currentStateProvider = Provider { state.value },
|
||||
)
|
||||
}
|
||||
|
||||
val params = paramsContainer.require<TokenReceiveComponent.Params>()
|
||||
|
||||
val stackNavigation = StackNavigation<TokenReceiveRoutes>()
|
||||
|
||||
internal val state: StateFlow<TokenReceiveUM>
|
||||
field = MutableStateFlow<TokenReceiveUM>(getInitState())
|
||||
field = MutableStateFlow<TokenReceiveUM>(tokenReceiveStateFactory.getInitialState(getTokenName()))
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
|
|
@ -67,20 +64,19 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onQrCodeClick(id: Int) {
|
||||
override fun onQrCodeClick(address: String) {
|
||||
analyticsEventHandler.send(
|
||||
TokenReceiveNewAnalyticsEvent.QrScreenOpened(
|
||||
token = getTokenName(),
|
||||
blockchainName = params.config.cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(addressId = id))
|
||||
stackNavigation.push(configuration = TokenReceiveRoutes.QrCode(address = address))
|
||||
}
|
||||
|
||||
override fun onCopyClick(id: Int, source: TokenReceiveCopyActionSource) {
|
||||
val addressToCopy = state.value.addresses[id] ?: return
|
||||
sendCopyActionAnalytic(addressToCopy, source)
|
||||
clipboardManager.setText(text = addressToCopy.value, isSensitive = true)
|
||||
override fun onCopyClick(address: ReceiveAddress, source: TokenReceiveCopyActionSource) {
|
||||
sendCopyActionAnalytic(address, source)
|
||||
clipboardManager.setText(text = address.value, isSensitive = true)
|
||||
}
|
||||
|
||||
override fun onShareClick(address: String) {
|
||||
|
|
@ -106,51 +102,8 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
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 -> {
|
||||
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 -> Ens
|
||||
ReceiveAddressModel.NameService.Legacy -> Primary.Legacy(
|
||||
displayName = TextReference.Res(R.string.domain_receive_assets_legacy_address),
|
||||
)
|
||||
}
|
||||
|
||||
put(
|
||||
key = index,
|
||||
value = ReceiveAddress(
|
||||
value = model.value,
|
||||
type = type,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentMap()
|
||||
}
|
||||
|
||||
private suspend fun load() = withContext(dispatchers.default) {
|
||||
state.value = state.value.copy(
|
||||
isEnsResultLoading = true,
|
||||
)
|
||||
|
||||
state.value = tokenReceiveStateFactory.getLoadingState()
|
||||
val reverseResolveResult =
|
||||
if (params.config.cryptoCurrency.network.nameResolvingType == Network.NameResolvingType.ENS) {
|
||||
getReverseResolvedEnsAddressUseCase(
|
||||
|
|
@ -161,74 +114,7 @@ internal class TokenReceiveModel @Inject constructor(
|
|||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val currentAddressValues = state.value.addresses.values.map { it.value }.toSet()
|
||||
|
||||
val newEnsAddresses = reverseResolveResult
|
||||
.filterIsInstance<EnsAddress.Address>()
|
||||
.filterNot { it.name in currentAddressValues }
|
||||
.map { ensAddress -> ReceiveAddress(value = ensAddress.name, type = Ens) }
|
||||
|
||||
val combinedAddresses = (state.value.addresses.values + newEnsAddresses)
|
||||
.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 }
|
||||
.toMap()
|
||||
.toPersistentMap()
|
||||
|
||||
state.value = state.value.copy(
|
||||
isEnsResultLoading = false,
|
||||
addresses = updatedAddresses,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotifications(): List<NotificationUM> {
|
||||
return buildList {
|
||||
add(
|
||||
NotificationUM.Info(
|
||||
title = resourceReference(
|
||||
R.string.receive_bottom_sheet_warning_title,
|
||||
wrappedList(
|
||||
getTokenName(),
|
||||
params.config.cryptoCurrency.network.name,
|
||||
),
|
||||
),
|
||||
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
|
||||
iconTint = NotificationConfig.IconTint.Accent,
|
||||
),
|
||||
)
|
||||
|
||||
params.config.tokenReceiveNotification.map { notification ->
|
||||
add(
|
||||
NotificationUM.Warning(
|
||||
title = resourceReference(notification.title),
|
||||
subtitle = resourceReference(notification.subtitle),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitState(): TokenReceiveUM {
|
||||
return TokenReceiveUM(
|
||||
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,
|
||||
notificationConfigs = getNotifications().toImmutableList(),
|
||||
)
|
||||
state.value = tokenReceiveStateFactory.getLoadedState(reverseResolveResult)
|
||||
}
|
||||
|
||||
private fun sendCopyActionAnalytic(receiveAddress: ReceiveAddress, source: TokenReceiveCopyActionSource) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ internal class TokenReceiveQrCodeModel @Inject constructor(
|
|||
addressName = TextReference.Str("${params.cryptoCurrency.name} (${params.cryptoCurrency.symbol})"),
|
||||
onCopyClick = {
|
||||
params.callback.onCopyClick(
|
||||
id = params.id,
|
||||
address = params.address,
|
||||
source = TokenReceiveCopyActionSource.QR,
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ internal sealed interface TokenReceiveRoutes : Route {
|
|||
data object ReceiveAssets : TokenReceiveRoutes
|
||||
|
||||
@Serializable
|
||||
data class QrCode(val addressId: Int) : TokenReceiveRoutes
|
||||
data class QrCode(val address: String) : TokenReceiveRoutes
|
||||
}
|
||||
|
|
@ -29,8 +29,8 @@ import com.tangem.core.res.getStringSafe
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.SpacerW12
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.buttons.small.TangemIconButton
|
||||
|
|
@ -45,9 +45,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import com.tangem.features.tokenreceive.ui.state.ReceiveAssetsUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
|
|
@ -121,9 +119,9 @@ private fun Info(
|
|||
|
||||
@Composable
|
||||
private fun AddressBlock(
|
||||
onOpenQrCodeClick: (id: Int) -> Unit,
|
||||
onCopyClick: (id: Int) -> Unit,
|
||||
addresses: ImmutableMap<Int, ReceiveAddress>,
|
||||
onOpenQrCodeClick: (address: String) -> Unit,
|
||||
onCopyClick: (address: ReceiveAddress) -> Unit,
|
||||
addresses: ImmutableList<ReceiveAddress>,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
|
@ -131,13 +129,13 @@ private fun AddressBlock(
|
|||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
|
||||
addresses.entries.toList().fastForEach { entry ->
|
||||
when (val type = entry.value.type) {
|
||||
addresses.fastForEach { address ->
|
||||
when (val type = address.type) {
|
||||
is ReceiveAddress.Type.Primary -> {
|
||||
key(entry.key) {
|
||||
key(address.value) {
|
||||
AddressItem(
|
||||
onCopyClick = {
|
||||
onCopyClick(entry.key)
|
||||
onCopyClick(address)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
|
|
@ -149,19 +147,19 @@ private fun AddressBlock(
|
|||
},
|
||||
onOpenQrCodeClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onOpenQrCodeClick(entry.key)
|
||||
onOpenQrCodeClick(address.value)
|
||||
},
|
||||
address = entry.value.value,
|
||||
address = address.value,
|
||||
primaryType = type,
|
||||
)
|
||||
SpacerH8()
|
||||
}
|
||||
}
|
||||
ReceiveAddress.Type.Ens -> {
|
||||
key(entry.key) {
|
||||
key(address.value) {
|
||||
EnsItem(
|
||||
onCopyClick = {
|
||||
onCopyClick(entry.key)
|
||||
onCopyClick(address)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
|
|
@ -171,7 +169,7 @@ private fun AddressBlock(
|
|||
)
|
||||
}
|
||||
},
|
||||
address = entry.value.value,
|
||||
address = address.value,
|
||||
)
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -213,9 +211,9 @@ private fun AddressItem(
|
|||
Column(modifier = Modifier.weight(1f)) {
|
||||
EllipsisText(
|
||||
text = primaryType.displayName.resolveReference(),
|
||||
ellipsis = TextEllipsis.Middle,
|
||||
ellipsis = TextEllipsis.End,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
EllipsisText(
|
||||
|
|
@ -229,16 +227,16 @@ private fun AddressItem(
|
|||
SpacerW12()
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
innerPadding = 6.dp,
|
||||
iconRes = R.drawable.ic_qrcode_new_24,
|
||||
onClick = onOpenQrCodeClick,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
SpacerW(12.dp)
|
||||
|
||||
TangemIconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size28),
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
iconRes = R.drawable.ic_copy_new_24,
|
||||
innerPadding = 6.dp,
|
||||
onClick = onCopyClick,
|
||||
|
|
@ -334,9 +332,9 @@ private class TokenReceiveAssetsContentProvider : PreviewParameterProvider<Recei
|
|||
subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description),
|
||||
),
|
||||
),
|
||||
addresses = persistentMapOf(
|
||||
0 to address,
|
||||
1 to address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
|
||||
addresses = persistentListOf(
|
||||
address,
|
||||
address.copy(type = ReceiveAddress.Type.Ens, value = "papasha.eth"),
|
||||
),
|
||||
showMemoDisclaimer = false,
|
||||
onCopyClick = {},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.features.tokenreceive.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -73,7 +75,10 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36)) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size36),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(
|
||||
R.string.receive_bottom_sheet_warning_message_compact,
|
||||
|
|
@ -87,12 +92,23 @@ private fun QrCodePage(addressFullName: TextReference, addressValue: String, net
|
|||
|
||||
SpacerH(20.dp)
|
||||
|
||||
Image(
|
||||
painter = qrCodePainter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.size(248.dp),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.border(
|
||||
width = 8.dp,
|
||||
color = TangemTheme.colors.icon.constant,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
)
|
||||
.padding(8.dp),
|
||||
|
||||
) {
|
||||
Image(
|
||||
painter = qrCodePainter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.sizeIn(minWidth = 148.dp),
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH24()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ package com.tangem.features.tokenreceive.ui.state
|
|||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
internal data class ReceiveAssetsUM(
|
||||
val showMemoDisclaimer: Boolean,
|
||||
val addresses: ImmutableMap<Int, ReceiveAddress>,
|
||||
val onOpenQrCodeClick: (id: Int) -> Unit,
|
||||
val onCopyClick: (id: Int) -> Unit,
|
||||
val addresses: ImmutableList<ReceiveAddress>,
|
||||
val onOpenQrCodeClick: (address: String) -> Unit,
|
||||
val onCopyClick: (address: ReceiveAddress) -> Unit,
|
||||
val isEnsResultLoading: Boolean,
|
||||
val notificationConfigs: ImmutableList<NotificationUM>,
|
||||
)
|
||||
|
|
@ -4,12 +4,11 @@ import com.tangem.common.ui.notifications.NotificationUM
|
|||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
internal data class TokenReceiveUM(
|
||||
val network: String,
|
||||
val iconState: CurrencyIconState,
|
||||
val addresses: ImmutableMap<Int, ReceiveAddress>,
|
||||
val addresses: ImmutableList<ReceiveAddress>,
|
||||
val isEnsResultLoading: Boolean,
|
||||
val notificationConfigs: ImmutableList<NotificationUM>,
|
||||
)
|
||||
|
|
@ -60,6 +60,7 @@ 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.TokenScreenAnalyticsEvent.DetailsScreenOpened.TokenBalance
|
||||
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.transaction.error.IncompleteTransactionError
|
||||
|
|
@ -86,6 +87,7 @@ import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
|
|||
import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -156,6 +158,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var isBalanceLoadedEventSent = false
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
|
||||
|
||||
/** Transaction id to check for status */
|
||||
|
|
@ -199,9 +202,6 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
val uiState: StateFlow<TokenDetailsState> = internalUiState
|
||||
|
||||
init {
|
||||
analyticsEventsHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol),
|
||||
)
|
||||
updateTopBarMenu()
|
||||
initButtons()
|
||||
updateContent()
|
||||
|
|
@ -317,6 +317,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
.onEach { maybeCurrencyStatus ->
|
||||
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus)
|
||||
maybeCurrencyStatus.onRight { status ->
|
||||
sendOneTimeBalanceLoadedAnalyticsEvent(status)
|
||||
cryptoCurrencyStatus = status
|
||||
updateButtons(currencyStatus = status)
|
||||
updateWarnings(status)
|
||||
|
|
@ -1078,6 +1079,44 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun sendOneTimeBalanceLoadedAnalyticsEvent(cryptoCurrencyStatus: CryptoCurrencyStatus?) {
|
||||
if (isBalanceLoadedEventSent || cryptoCurrencyStatus == null) return
|
||||
|
||||
val tokenBalance = when (val value = cryptoCurrencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
-> {
|
||||
if (value.sources.networkSource.isActual()) {
|
||||
val amount = value.amount
|
||||
if (amount != null && !amount.isZero()) {
|
||||
TokenBalance.Full
|
||||
} else {
|
||||
TokenBalance.Empty
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
-> TokenBalance.NoAddress
|
||||
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
-> TokenBalance.Error
|
||||
CryptoCurrencyStatus.Loading -> return
|
||||
}
|
||||
analyticsEventsHandler.send(
|
||||
event = TokenScreenAnalyticsEvent.DetailsScreenOpened(
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
token = cryptoCurrency.symbol,
|
||||
tokenBalance = tokenBalance,
|
||||
),
|
||||
)
|
||||
isBalanceLoadedEventSent = true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ internal class WcAddNetworkModel @Inject constructor(
|
|||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
emulationStatus = null,
|
||||
securityStatus = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -408,6 +408,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
emulationStatus = emulationStatus,
|
||||
securityStatus = useCase.session.securityStatus,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ internal class WcSignTransactionModel @Inject constructor(
|
|||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
emulationStatus = null,
|
||||
securityStatus = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue