Updated on 2026-08-14
This commit is contained in:
commit
38df746381
41 changed files with 392 additions and 102 deletions
|
|
@ -500,4 +500,10 @@ internal object TokensDomainModule {
|
|||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAssetRequirementsUseCase(walletManagersFacade: WalletManagersFacade): GetAssetRequirementsUseCase {
|
||||
return GetAssetRequirementsUseCase(walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -223,5 +223,6 @@ sealed class AnalyticsParam {
|
|||
const val NONCE = "Nonce"
|
||||
const val STANDARD = "Standard"
|
||||
const val NO_COLLECTION = "No collection"
|
||||
const val EMULATION_STATUS = "Emulation Status"
|
||||
}
|
||||
}
|
||||
|
|
@ -41,12 +41,6 @@ internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
|
|||
|
||||
operator fun invoke(initModel: SignModel) = channelFlow {
|
||||
val state = MutableStateFlow(WcSignState(initModel, WcSignStep.PreSign))
|
||||
analytics.send(
|
||||
WcAnalyticEvents.SignatureRequestReceived(
|
||||
rawRequest = context.rawSdkRequest,
|
||||
network = context.network,
|
||||
),
|
||||
)
|
||||
|
||||
state
|
||||
.onEach { newState -> channel.send(newState) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
||||
class GetAssetRequirementsUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
): Either<Throwable, AssetRequirementsCondition?> {
|
||||
return Either.Companion.catch {
|
||||
walletManagersFacade.getAssetRequirements(userWalletId, currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,15 +56,24 @@ internal open class BaseActionsFactory(
|
|||
*
|
||||
* @param userWallet the user's cold wallet
|
||||
* @param currency the cryptocurrency to check
|
||||
* @param requirementsDeferred a deferred object containing the asset requirements condition
|
||||
*/
|
||||
protected suspend fun getOnrampUnavailabilityReason(
|
||||
userWallet: UserWallet,
|
||||
currency: CryptoCurrency,
|
||||
requirementsDeferred: Deferred<AssetRequirementsCondition?>?,
|
||||
): ScenarioUnavailabilityReason {
|
||||
return rampStateManager.availableForBuy(
|
||||
val onrampUnavailabilityReason = rampStateManager.availableForBuy(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = currency,
|
||||
)
|
||||
val shouldCheckAssetRequirements =
|
||||
onrampUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null
|
||||
return if (shouldCheckAssetRequirements) {
|
||||
getReceiveScenario(requirementsDeferred.await())
|
||||
} else {
|
||||
onrampUnavailabilityReason
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -52,7 +52,11 @@ internal class CommonActionsFactory(
|
|||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency)
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
||||
val sendUnavailabilityReasonDeferred = async {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ internal class OutdatedDataActionsFactory(
|
|||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency)
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
|
||||
val sendUnavailabilityReasonDeferred = if (sources.networkSource == StatusSource.ACTUAL) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ internal class UnreachableActionsFactory(
|
|||
}
|
||||
|
||||
val onrampUnavailabilityReasonDeferred = async {
|
||||
getOnrampUnavailabilityReason(userWallet = userWallet, currency = cryptoCurrencyStatus.currency)
|
||||
getOnrampUnavailabilityReason(
|
||||
userWallet = userWallet,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
requirementsDeferred = requirementsDeferred,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -87,11 +87,11 @@ sealed class WcAnalyticEvents(
|
|||
),
|
||||
)
|
||||
|
||||
class SignatureRequestReceived(
|
||||
class TransactionDetailsOpened(
|
||||
rawRequest: WcSdkSessionRequest,
|
||||
network: Network,
|
||||
) : WcAnalyticEvents(
|
||||
event = "Signature Request Received",
|
||||
event = "Transaction Details Opened",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name,
|
||||
AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url,
|
||||
|
|
@ -100,6 +100,27 @@ sealed class WcAnalyticEvents(
|
|||
),
|
||||
)
|
||||
|
||||
class SignatureRequestReceived(
|
||||
rawRequest: WcSdkSessionRequest,
|
||||
network: Network,
|
||||
emulationStatus: EmulationStatus,
|
||||
) : WcAnalyticEvents(
|
||||
event = "Signature Request Received",
|
||||
params = mapOf(
|
||||
AnalyticsParam.Key.DAPP_NAME to rawRequest.dAppMetaData.name,
|
||||
AnalyticsParam.Key.DAPP_URL to rawRequest.dAppMetaData.url,
|
||||
AnalyticsParam.Key.METHOD_NAME to rawRequest.request.method,
|
||||
AnalyticsParam.Key.BLOCKCHAIN to network.name,
|
||||
AnalyticsParam.Key.EMULATION_STATUS to emulationStatus.status,
|
||||
),
|
||||
) {
|
||||
enum class EmulationStatus(val status: String) {
|
||||
Emulated("Emulated"),
|
||||
Error("Error"),
|
||||
CanNotEmulate("Can't emulate"),
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureRequestHandled(
|
||||
rawRequest: WcSdkSessionRequest,
|
||||
network: Network,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.transaction.models)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.tokens.GetAssetRequirementsUseCase
|
||||
import com.tangem.domain.tokens.GetTokenListUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
|
||||
|
|
@ -53,6 +55,7 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
private val getWalletsUseCase: GetWalletsUseCase,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase,
|
||||
private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<TokenListUM> = tokenListUMController.state
|
||||
|
|
@ -204,17 +207,28 @@ internal class OnrampTokenListModel @Inject constructor(
|
|||
return coroutineScope {
|
||||
map { status ->
|
||||
async {
|
||||
val isAvailable = checkAvailabilityByOperation(status = status)
|
||||
val isOperationAvailable = checkAvailabilityByOperation(status = status)
|
||||
val isNotMissedDerivation = status.value !is CryptoCurrencyStatus.MissedDerivation
|
||||
val isNotLoading = status.value !is CryptoCurrencyStatus.Loading
|
||||
val requirements = getAssetRequirementsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = status.currency,
|
||||
).getOrNull()
|
||||
|
||||
val isNotUnreachable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> true // unreachable state is available for Buy operation
|
||||
OnrampOperation.SELL -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
OnrampOperation.SWAP -> status.value !is CryptoCurrencyStatus.Unreachable
|
||||
val isNotTrustlineRequired = requirements !is AssetRequirementsCondition.RequiredTrustline
|
||||
val isNotUnreachable = status.value !is CryptoCurrencyStatus.Unreachable
|
||||
|
||||
val isAvailable = when (params.filterOperation) {
|
||||
OnrampOperation.BUY -> {
|
||||
isNotTrustlineRequired
|
||||
} // unreachable state is available for Buy operation
|
||||
OnrampOperation.SELL -> isNotUnreachable
|
||||
OnrampOperation.SWAP -> {
|
||||
isNotUnreachable && isNotTrustlineRequired
|
||||
}
|
||||
}
|
||||
|
||||
status to (isAvailable && isNotMissedDerivation && isNotLoading && isNotUnreachable)
|
||||
status to (isOperationAvailable && isNotMissedDerivation && isNotLoading && isAvailable)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
|
|
|
|||
|
|
@ -13,11 +13,18 @@ import java.math.BigInteger
|
|||
@Immutable
|
||||
sealed class FeeSelectorUM {
|
||||
|
||||
data object Loading : FeeSelectorUM()
|
||||
abstract val isPrimaryButtonEnabled: Boolean
|
||||
|
||||
data class Error(val error: GetFeeError) : FeeSelectorUM()
|
||||
data object Loading : FeeSelectorUM() {
|
||||
override val isPrimaryButtonEnabled = false
|
||||
}
|
||||
|
||||
data class Error(val error: GetFeeError) : FeeSelectorUM() {
|
||||
override val isPrimaryButtonEnabled = false
|
||||
}
|
||||
|
||||
data class Content(
|
||||
override val isPrimaryButtonEnabled: Boolean,
|
||||
val fees: TransactionFee,
|
||||
val feeItems: ImmutableList<FeeItem>,
|
||||
val selectedFeeItem: FeeItem,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.features.send.v2.feeselector.model.transformers
|
||||
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.v2.api.entity.FeeItem
|
||||
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.v2.feeselector.model.FeeSelectorIntents
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
|
@ -30,7 +32,16 @@ internal class FeeSelectorCustomValueChangedTransformer(
|
|||
fee = customFeeConverter.convertBack(updatedCustomValues),
|
||||
customValues = updatedCustomValues,
|
||||
)
|
||||
|
||||
val customFeeValue = updatedCustomValues.firstOrNull()
|
||||
val isNotEmptyCustom = if (customFeeValue != null) {
|
||||
!customFeeValue.value.parseToBigDecimal(customFeeValue.decimals).isZero()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
return state.copy(
|
||||
isPrimaryButtonEnabled = isNotEmptyCustom,
|
||||
feeItems = state.feeItems.map { if (it is FeeItem.Custom) newCustomFee else it }.toImmutableList(),
|
||||
selectedFeeItem = newCustomFee,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ internal class FeeSelectorLoadedTransformer(
|
|||
val nonce = ((prevState as? FeeSelectorUM.Content)?.feeNonce as? FeeNonce.Nonce)?.nonce
|
||||
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = fees,
|
||||
feeItems = feeItems,
|
||||
selectedFeeItem = selectedFee,
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
|
|||
|
||||
override val values: Sequence<FeeSelectorUM> = sequenceOf(
|
||||
FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
feeItems = persistentListOf(lowFeeItem),
|
||||
selectedFeeItem = lowFeeItem,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
|
|
@ -218,6 +219,7 @@ private class FeeSelectorUMProvider : PreviewParameterProvider<FeeSelectorUM> {
|
|||
fees = TransactionFee.Single(lowFeeItem.fee),
|
||||
),
|
||||
FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = false,
|
||||
feeItems = persistentListOf(maxFeeItem),
|
||||
selectedFeeItem = maxFeeItem,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ internal fun FeeSelectorModalBottomSheet(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
enabled = state.isPrimaryButtonEnabled,
|
||||
text = stringResourceSafe(R.string.common_done),
|
||||
onClick = feeSelectorIntents::onDoneClick,
|
||||
)
|
||||
|
|
@ -484,6 +485,7 @@ private fun FeeSelectorBS_Preview(
|
|||
private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<FeeSelectorUM.Content>(
|
||||
collection = listOf(
|
||||
FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = false,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Suggested(
|
||||
title = stringReference("Suggested by Tangem"),
|
||||
|
|
@ -494,9 +496,6 @@ private class FeeSelectorUMContentProvider : CollectionPreviewParameterProvider<
|
|||
FeeItem.Fast(fee = Fee.Common(Amount(value = BigDecimal("0.03"), blockchain = Blockchain.Ethereum))),
|
||||
customFeeItem,
|
||||
),
|
||||
// selectedFeeItem = FeeItem.Market(
|
||||
// amount = Amount(value = BigDecimal("0.02"), blockchain = Blockchain.Ethereum),
|
||||
// ),
|
||||
selectedFeeItem = customFeeItem,
|
||||
feeExtraInfo = FeeExtraInfo(
|
||||
isFeeApproximate = true,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.eth
|
|||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GAS_DECIMALS
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.ethereum.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS
|
||||
import com.tangem.features.send.v2.subcomponents.fee.model.converters.custom.setEmpty
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -91,7 +92,7 @@ internal class EthereumEIPCustomFeeConverter(
|
|||
val mutableCustomValues = customValues.toMutableList()
|
||||
return mutableCustomValues.apply {
|
||||
when (index) {
|
||||
FEE_AMOUNT -> setOnAmountChange(value, index)
|
||||
FEE_AMOUNT -> setOnAmountChange(feeValue, value, index)
|
||||
MAX_FEE -> setOnMaxFeeChange(value, index)
|
||||
GAS_LIMIT -> setOnGasLimitChange(value, index)
|
||||
else -> set(index, this[index].copy(value = value))
|
||||
|
|
@ -99,8 +100,12 @@ internal class EthereumEIPCustomFeeConverter(
|
|||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun MutableList<CustomFeeFieldUM>.setOnAmountChange(value: String, index: Int) {
|
||||
val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals)
|
||||
private fun MutableList<CustomFeeFieldUM>.setOnAmountChange(
|
||||
feeValue: Fee.Ethereum.EIP1559,
|
||||
value: String,
|
||||
index: Int,
|
||||
) {
|
||||
val gasLimitRaw = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals)
|
||||
if (value.isBlank()) {
|
||||
setEmpty(FEE_AMOUNT)
|
||||
setEmpty(MAX_FEE)
|
||||
|
|
@ -108,6 +113,27 @@ internal class EthereumEIPCustomFeeConverter(
|
|||
val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals)
|
||||
val newFeeAmount = newFeeAmountDecimal.movePointRight(GIGA_DECIMALS) // from ETH to GWEI
|
||||
|
||||
val gasLimit = if (gasLimitRaw.isZero()) {
|
||||
val gasLimitTemp = feeValue.gasLimit.toBigDecimal()
|
||||
set(
|
||||
index = GAS_LIMIT,
|
||||
element = this[GAS_LIMIT].copy(value = gasLimitTemp.parseBigDecimal(GIGA_DECIMALS)),
|
||||
)
|
||||
gasLimitTemp
|
||||
} else {
|
||||
gasLimitRaw
|
||||
}
|
||||
|
||||
if (this[PRIORITY_FEE].value.isBlank()) {
|
||||
set(
|
||||
index = PRIORITY_FEE,
|
||||
element = this[PRIORITY_FEE].copy(
|
||||
value = feeValue.priorityFee.toBigDecimal().movePointLeft(GIGA_DECIMALS)
|
||||
.parseBigDecimal(GIGA_DECIMALS),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val newMaxFee = newFeeAmount.divide(gasLimit, this[MAX_FEE].decimals, RoundingMode.HALF_UP)
|
||||
|
||||
set(
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(FeeItem.Market(fee)),
|
||||
selectedFeeItem = FeeItem.Market(fee),
|
||||
|
|
@ -282,6 +283,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
@ -364,6 +366,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
priority = fee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
@ -434,6 +437,7 @@ class SendConfirmationNotificationsTransformerV2Test {
|
|||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUM.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class TransformersComparisonTest {
|
|||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
|
||||
assertThat(contentV1.notifications.size).isEqualTo(contentV2.notifications.size)
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
|
||||
|
|
@ -117,6 +118,8 @@ class TransformersComparisonTest {
|
|||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV1.notifications).hasSize(1)
|
||||
assertThat(contentV2.notifications).hasSize(1)
|
||||
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.FeeTooLow::class.java)
|
||||
|
|
@ -165,6 +168,8 @@ class TransformersComparisonTest {
|
|||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV1.notifications).hasSize(1)
|
||||
assertThat(contentV2.notifications).hasSize(1)
|
||||
assertThat(contentV1.notifications.first()).isInstanceOf(NotificationUM.Warning.TooHigh::class.java)
|
||||
|
|
@ -215,6 +220,8 @@ class TransformersComparisonTest {
|
|||
val contentV1 = resultV1 as ConfirmUM.Content
|
||||
val contentV2 = resultV2 as ConfirmUM.Content
|
||||
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV2.isPrimaryButtonEnabled).isTrue()
|
||||
assertThat(contentV1.notifications).hasSize(2)
|
||||
assertThat(contentV2.notifications).hasSize(2)
|
||||
|
||||
|
|
@ -268,6 +275,7 @@ class TransformersComparisonTest {
|
|||
|
||||
assertThat(contentV1.notifications).isEmpty()
|
||||
assertThat(contentV2.notifications).isEmpty()
|
||||
assertThat(contentV1.isPrimaryButtonEnabled).isEqualTo(contentV2.isPrimaryButtonEnabled)
|
||||
assertThat(contentV1.sendingFooter).isEqualTo(contentV2.sendingFooter)
|
||||
}
|
||||
|
||||
|
|
@ -377,6 +385,7 @@ class TransformersComparisonTest {
|
|||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUMV2.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Market(fee),
|
||||
|
|
@ -470,6 +479,7 @@ class TransformersComparisonTest {
|
|||
priority = fee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
@ -605,6 +615,7 @@ class TransformersComparisonTest {
|
|||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
@ -740,6 +751,7 @@ class TransformersComparisonTest {
|
|||
priority = priorityFee,
|
||||
)
|
||||
return FeeSelectorUMV2.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Custom(
|
||||
|
|
@ -839,6 +851,7 @@ class TransformersComparisonTest {
|
|||
)
|
||||
val transactionFee = TransactionFee.Single(fee)
|
||||
return FeeSelectorUMV2.Content(
|
||||
isPrimaryButtonEnabled = true,
|
||||
fees = transactionFee,
|
||||
feeItems = persistentListOf(
|
||||
FeeItem.Market(fee),
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@ private fun SendWithSwapSuccessContent_Preview() {
|
|||
),
|
||||
feeFiatRateUM = null,
|
||||
feeNonce = FeeNonce.None,
|
||||
isPrimaryButtonEnabled = false,
|
||||
),
|
||||
confirmUM = ConfirmUM.Success(
|
||||
isPrimaryButtonEnabled = true,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
|
||||
/** Data */
|
||||
implementation(projects.data.common)
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
|
|||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.swap.converters.*
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
|
|
@ -134,7 +135,12 @@ internal class DefaultSwapRepository(
|
|||
contractAddress = initialCurrency.contractAddress,
|
||||
network = initialCurrency.network,
|
||||
)
|
||||
val currenciesList = currencyList.map { leastTokenInfoConverter.convert(it) }
|
||||
val currenciesList = currencyList
|
||||
.filter {
|
||||
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, it)
|
||||
requirements !is AssetRequirementsCondition.RequiredTrustline
|
||||
}
|
||||
.map { leastTokenInfoConverter.convert(it) }
|
||||
|
||||
val pairsDeferred = async {
|
||||
getPairsInternal(
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
|
@ -79,6 +80,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private val getEthSpecificFeeUseCase: GetEthSpecificFeeUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val getAssetRequirementsUseCase: GetAssetRequirementsUseCase,
|
||||
private val amountFormatter: AmountFormatter,
|
||||
@Assisted private val userWalletId: UserWalletId,
|
||||
) : SwapInteractor {
|
||||
|
|
@ -139,7 +141,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getToCurrenciesGroup(
|
||||
private suspend fun getToCurrenciesGroup(
|
||||
currency: CryptoCurrency,
|
||||
leastPairs: List<SwapPairLeast>,
|
||||
cryptoCurrenciesList: List<CryptoCurrencyStatus>,
|
||||
|
|
@ -171,15 +173,18 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun findProvidersForPair(
|
||||
private suspend fun findProvidersForPair(
|
||||
cryptoCurrencyStatuses: CryptoCurrencyStatus,
|
||||
swapPairsLeastList: List<SwapPairLeast>,
|
||||
tokenInfoForAvailable: (SwapPairLeast) -> LeastTokenInfo,
|
||||
): List<SwapProvider>? {
|
||||
val requirements = getAssetRequirementsUseCase.invoke(userWalletId, cryptoCurrencyStatuses.currency).getOrNull()
|
||||
|
||||
return swapPairsLeastList.firstNotNullOfOrNull {
|
||||
val listTokenInfo = tokenInfoForAvailable(it)
|
||||
if (cryptoCurrencyStatuses.currency.network.backendId == listTokenInfo.network &&
|
||||
cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress
|
||||
cryptoCurrencyStatuses.currency.getContractAddress() == listTokenInfo.contractAddress &&
|
||||
requirements !is AssetRequirementsCondition.RequiredTrustline
|
||||
) {
|
||||
it.providers
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -32,12 +32,13 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -145,8 +146,11 @@ private fun MissingRequiredBlock(missingNetworks: ImmutableList<WcNetworkInfoIte
|
|||
val missingNetworksName = missingNetworks.joinToString { it.name }
|
||||
val notificationUM = remember(missingNetworks) {
|
||||
NotificationUM.Info(
|
||||
title = stringReference("The wallet has no required networks"),
|
||||
subtitle = stringReference("Add the $missingNetworksName network to your portfolio for this wallet."),
|
||||
title = resourceReference(R.string.wc_missing_required_network_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.wc_missing_required_network_description,
|
||||
wrappedList(missingNetworksName),
|
||||
),
|
||||
)
|
||||
}
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -288,7 +292,7 @@ private fun NetworkNameAndSymbol(name: String, symbol: String, modifier: Modifie
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO)
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun WcSelectNetworksContent_Preview(
|
||||
|
|
@ -296,29 +300,7 @@ private fun WcSelectNetworksContent_Preview(
|
|||
state: WcSelectNetworksUM,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = stringReference("Choose wallet"),
|
||||
onEndClick = {},
|
||||
endIconRes = R.drawable.ic_close_24,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
WcSelectNetworksContent(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
)
|
||||
WcSelectNetworksBS(state = state, onBack = {}, onDismiss = {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +310,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
|
|||
missing = persistentListOf(
|
||||
WcNetworkInfoItem.Required(
|
||||
id = "ethereum",
|
||||
name = "EthereumEthereumEthereumEthereumEthereumEthereumEthereumEthereum",
|
||||
name = "Ethereum",
|
||||
symbol = "ETH",
|
||||
icon = R.drawable.ic_solana_16,
|
||||
),
|
||||
|
|
@ -380,6 +362,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
|
|||
),
|
||||
),
|
||||
onDone = {},
|
||||
doneButtonEnabled = false,
|
||||
),
|
||||
WcSelectNetworksUM(
|
||||
missing = persistentListOf(),
|
||||
|
|
@ -424,6 +407,7 @@ private class WcSelectNetworksProvider : CollectionPreviewParameterProvider<WcSe
|
|||
),
|
||||
),
|
||||
onDone = {},
|
||||
doneButtonEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -57,6 +57,7 @@ sealed class WcAppInfoSecurityNotification(
|
|||
|
||||
@Immutable
|
||||
internal sealed class WcNetworksInfo {
|
||||
data object NoneNetworksAdded : WcNetworksInfo()
|
||||
data class MissingRequiredNetworkInfo(val networks: String) : WcNetworksInfo()
|
||||
data class ContainsAllRequiredNetworks(val items: ImmutableList<WcNetworkInfoItem>) : WcNetworksInfo()
|
||||
}
|
||||
|
|
@ -8,5 +8,5 @@ internal data class WcSelectNetworksUM(
|
|||
val available: ImmutableList<WcNetworkInfoItem.Checkable>,
|
||||
val notAdded: ImmutableList<WcNetworkInfoItem.ReadOnly>,
|
||||
val onDone: () -> Unit,
|
||||
val doneButtonEnabled: Boolean = true,
|
||||
val doneButtonEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.walletconnect.connections.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.router.stack.pushNew
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -10,18 +12,16 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairState
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.walletconnect.connections.components.WcPairComponent
|
||||
import com.tangem.features.walletconnect.connections.components.WcSelectNetworksComponent
|
||||
|
|
@ -91,7 +91,9 @@ internal class WcPairModel @Inject constructor(
|
|||
appInfoUiState.transformerUpdate(
|
||||
WcConnectButtonProgressTransformer(showProgress = false),
|
||||
)
|
||||
pairState.result.onLeft(::processError)
|
||||
pairState.result
|
||||
.onLeft(::processError)
|
||||
.onRight(::processSuccessfullyConnected)
|
||||
router.pop()
|
||||
}
|
||||
is WcPairState.Error -> {
|
||||
|
|
@ -196,6 +198,13 @@ internal class WcPairModel @Inject constructor(
|
|||
stackNavigation.pushNew(WcAppInfoRoutes.Alert(WcAppInfoRoutes.Alert.Type.Verified(appName)))
|
||||
}
|
||||
|
||||
private fun processSuccessfullyConnected(session: WcSession) {
|
||||
// TODO: [REDACTED_JIRA] localization
|
||||
messageSender.send(
|
||||
SnackbarMessage(message = stringReference("Connected to ${session.sdkModel.appMetaData.name}")),
|
||||
)
|
||||
}
|
||||
|
||||
private fun processError(error: WcPairError) {
|
||||
val alert = when (error) {
|
||||
is WcPairError.UnsupportedDApp -> {
|
||||
|
|
@ -236,6 +245,8 @@ internal class WcPairModel @Inject constructor(
|
|||
WcNetworksSelectedTransformer(
|
||||
missingNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
notAddedNetworks = proposalNetwork.notAdded,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ internal class WcSelectNetworksModel @Inject constructor(
|
|||
|
||||
private fun getInitialState(): WcSelectNetworksUM {
|
||||
val editEnabled = params.missingRequiredNetworks.isEmpty()
|
||||
val doneButtonEnabled = params.missingRequiredNetworks.isEmpty() &&
|
||||
(params.requiredNetworks.isNotEmpty() || params.availableNetworks.isNotEmpty())
|
||||
|
||||
return WcSelectNetworksUM(
|
||||
missing = params.missingRequiredNetworks.map { network ->
|
||||
WcNetworkInfoItem.Required(
|
||||
|
|
@ -98,7 +101,7 @@ internal class WcSelectNetworksModel @Inject constructor(
|
|||
)
|
||||
}.toImmutableList(),
|
||||
onDone = ::onDone,
|
||||
doneButtonEnabled = editEnabled,
|
||||
doneButtonEnabled = doneButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@ package com.tangem.features.walletconnect.connections.model.transformers
|
|||
|
||||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.features.walletconnect.connections.entity.WcAppInfoSecurityNotification
|
||||
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
|
||||
import com.tangem.features.walletconnect.connections.entity.WcPrimaryButtonConfig
|
||||
|
|
@ -37,13 +37,22 @@ internal class WcAppInfoTransformer(
|
|||
value = WcNetworksInfoConverter.Input(
|
||||
missingNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
notAddedNetworks = proposalNetwork.notAdded,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
onNetworksClick = onNetworksClick,
|
||||
connectButtonConfig = WcPrimaryButtonConfig(
|
||||
showProgress = false,
|
||||
enabled = proposalNetwork.missingRequired.isEmpty(),
|
||||
enabled = WcConnectButtonAvailabilityConverter.convert(
|
||||
WcConnectButtonAvailabilityConverter.Input(
|
||||
missingNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
selectedNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
onClick = { onConnect(dAppSession.securityStatus) },
|
||||
),
|
||||
onDismiss = onDismiss,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.walletconnect.connections.model.transformers
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal
|
||||
import com.tangem.features.walletconnect.connections.entity.WcAppInfoUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
|
|
@ -19,11 +19,20 @@ internal class WcAppInfoWalletChangedTransformer(
|
|||
WcNetworksInfoConverter.Input(
|
||||
missingNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
notAddedNetworks = proposalNetwork.notAdded,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
connectButtonConfig = prevState.connectButtonConfig.copy(
|
||||
enabled = proposalNetwork.missingRequired.isEmpty(),
|
||||
enabled = WcConnectButtonAvailabilityConverter.convert(
|
||||
WcConnectButtonAvailabilityConverter.Input(
|
||||
missingNetworks = proposalNetwork.missingRequired,
|
||||
requiredNetworks = proposalNetwork.required,
|
||||
availableNetworks = proposalNetwork.available,
|
||||
selectedNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.walletconnect.connections.model.transformers
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
internal object WcConnectButtonAvailabilityConverter : Converter<WcConnectButtonAvailabilityConverter.Input, Boolean> {
|
||||
|
||||
override fun convert(value: Input): Boolean {
|
||||
val missing = value.missingNetworks
|
||||
val required = value.requiredNetworks
|
||||
val available = value.availableNetworks
|
||||
val selected = value.selectedNetworks
|
||||
|
||||
return missing.isEmpty() && (required.isNotEmpty() || available.isNotEmpty() || selected.isNotEmpty())
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val missingNetworks: Set<Network>,
|
||||
val requiredNetworks: Set<Network>,
|
||||
val availableNetworks: Set<Network>,
|
||||
val selectedNetworks: Set<Network>,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,31 +7,38 @@ import com.tangem.features.walletconnect.connections.entity.WcNetworksInfo
|
|||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Suppress("DestructuringDeclarationWithTooManyEntries")
|
||||
internal object WcNetworksInfoConverter : Converter<WcNetworksInfoConverter.Input, WcNetworksInfo> {
|
||||
override fun convert(value: Input): WcNetworksInfo {
|
||||
return if (value.missingNetworks.isNotEmpty()) {
|
||||
WcNetworksInfo.MissingRequiredNetworkInfo(
|
||||
networks = value.missingNetworks.joinToString { it.name },
|
||||
)
|
||||
} else {
|
||||
WcNetworksInfo.ContainsAllRequiredNetworks(
|
||||
items = (value.requiredNetworks + value.additionallyEnabledNetworks)
|
||||
.map {
|
||||
val missing = value.missingNetworks
|
||||
val required = value.requiredNetworks
|
||||
val available = value.availableNetworks
|
||||
val notAdded = value.notAddedNetworks
|
||||
val additionallyEnabled = value.additionallyEnabledNetworks
|
||||
return when {
|
||||
missing.isNotEmpty() -> WcNetworksInfo.MissingRequiredNetworkInfo(missing.joinToString { it.name })
|
||||
required.isEmpty() && available.isEmpty() && notAdded.isNotEmpty() -> WcNetworksInfo.NoneNetworksAdded
|
||||
else -> {
|
||||
val combinedNetworks = required + additionallyEnabled
|
||||
WcNetworksInfo.ContainsAllRequiredNetworks(
|
||||
items = combinedNetworks.map { network ->
|
||||
WcNetworkInfoItem.Required(
|
||||
id = it.rawId,
|
||||
icon = it.iconResId,
|
||||
name = it.name,
|
||||
symbol = it.currencySymbol,
|
||||
id = network.rawId,
|
||||
icon = network.iconResId,
|
||||
name = network.name,
|
||||
symbol = network.currencySymbol,
|
||||
)
|
||||
}
|
||||
.toImmutableList(),
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Input(
|
||||
val missingNetworks: Set<Network>,
|
||||
val requiredNetworks: Set<Network>,
|
||||
val availableNetworks: Set<Network>,
|
||||
val notAddedNetworks: Set<Network>,
|
||||
val additionallyEnabledNetworks: Set<Network>,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,19 +7,29 @@ import com.tangem.utils.transformer.Transformer
|
|||
internal class WcNetworksSelectedTransformer(
|
||||
private val missingNetworks: Set<Network>,
|
||||
private val requiredNetworks: Set<Network>,
|
||||
private val availableNetworks: Set<Network>,
|
||||
private val notAddedNetworks: Set<Network>,
|
||||
private val additionallyEnabledNetworks: Set<Network>,
|
||||
) : Transformer<WcAppInfoUM> {
|
||||
override fun transform(prevState: WcAppInfoUM): WcAppInfoUM {
|
||||
val contentState = prevState as? WcAppInfoUM.Content ?: return prevState
|
||||
return contentState.copy(
|
||||
connectButtonConfig = contentState.connectButtonConfig.copy(
|
||||
enabled = missingNetworks.isEmpty() &&
|
||||
(requiredNetworks.isNotEmpty() || additionallyEnabledNetworks.isNotEmpty()),
|
||||
enabled = WcConnectButtonAvailabilityConverter.convert(
|
||||
WcConnectButtonAvailabilityConverter.Input(
|
||||
missingNetworks = missingNetworks,
|
||||
requiredNetworks = requiredNetworks,
|
||||
availableNetworks = availableNetworks,
|
||||
selectedNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
),
|
||||
networksInfo = WcNetworksInfoConverter.convert(
|
||||
value = WcNetworksInfoConverter.Input(
|
||||
missingNetworks = missingNetworks,
|
||||
requiredNetworks = requiredNetworks,
|
||||
availableNetworks = availableNetworks,
|
||||
notAddedNetworks = notAddedNetworks,
|
||||
additionallyEnabledNetworks = additionallyEnabledNetworks,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ internal fun WcAppInfoItem(
|
|||
text = title,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h3,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (verifiedDAppState is VerifiedDAppState.Verified) {
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -35,10 +35,7 @@ import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTi
|
|||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -284,6 +281,19 @@ private fun WcAppInfoSecondBlock(state: WcAppInfoUM.Content, modifier: Modifier
|
|||
)
|
||||
when (state.networksInfo) {
|
||||
is WcNetworksInfo.ContainsAllRequiredNetworks -> Unit
|
||||
is WcNetworksInfo.NoneNetworksAdded -> {
|
||||
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
|
||||
Notification(
|
||||
config = NotificationConfig(
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
// TODO: [REDACTED_JIRA] localization
|
||||
title = stringReference("Specify selected networks"),
|
||||
subtitle = stringReference("At least one network is required for dApp connection"),
|
||||
),
|
||||
iconTint = TangemTheme.colors.icon.attention,
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
)
|
||||
}
|
||||
is WcNetworksInfo.MissingRequiredNetworkInfo -> {
|
||||
HorizontalDivider(thickness = 1.dp, color = TangemTheme.colors.stroke.primary)
|
||||
Notification(
|
||||
|
|
@ -370,6 +380,7 @@ private fun SelectNetworksBlock(networksInfo: WcNetworksInfo, modifier: Modifier
|
|||
when (networksInfo) {
|
||||
is WcNetworksInfo.ContainsAllRequiredNetworks -> NetworkIcons(items = networksInfo.items)
|
||||
is WcNetworksInfo.MissingRequiredNetworkInfo -> Unit
|
||||
is WcNetworksInfo.NoneNetworksAdded -> Unit
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(width = 18.dp, height = 24.dp),
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ private fun ConnectionItem(connection: WcConnectionsUM, modifier: Modifier = Mod
|
|||
private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
AsyncImage(
|
||||
|
|
@ -213,9 +214,12 @@ private fun AppInfoItem(appInfo: WcConnectedAppInfo, modifier: Modifier = Modifi
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
text = appInfo.name,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (appInfo.verifiedState is VerifiedDAppState.Verified) {
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ internal object WcConnectionsPreviewData {
|
|||
walletName = "Bitcoin",
|
||||
connectedApps = persistentListOf(
|
||||
WcConnectedAppInfo(
|
||||
name = "React app",
|
||||
name = "React app React app React app React app React app",
|
||||
iconUrl = "$BASE_URL/LiteCoin.png",
|
||||
subtitle = "https://react-app.walletconnect.com/",
|
||||
verifiedState = VerifiedDAppState.Verified {},
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package com.tangem.features.walletconnect.transaction.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pushNew
|
||||
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.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcAddNetworkUseCase
|
||||
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
|
||||
|
|
@ -24,6 +26,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
|
|
@ -34,6 +37,7 @@ internal class WcAddNetworkModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val useCaseFactory: WcRequestUseCaseFactory,
|
||||
private val wcAddEthereumChainUMConverter: WcAddEthereumChainUMConverter,
|
||||
) : Model(), WcCommonTransactionModel {
|
||||
|
|
@ -44,10 +48,11 @@ internal class WcAddNetworkModel @Inject constructor(
|
|||
val stackNavigation = StackNavigation<WcTransactionRoutes>()
|
||||
|
||||
private val params = paramsContainer.require<WcTransactionModelParams>()
|
||||
private var useCase by Delegates.notNull<WcAddNetworkUseCase>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
val useCase: WcAddNetworkUseCase = useCaseFactory.createUseCase<WcAddNetworkUseCase>(params.rawRequest)
|
||||
useCase = useCaseFactory.createUseCase<WcAddNetworkUseCase>(params.rawRequest)
|
||||
.onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) }
|
||||
.getOrNull() ?: return@launch
|
||||
_uiState.emit(
|
||||
|
|
@ -75,6 +80,12 @@ internal class WcAddNetworkModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun showTransactionRequest() {
|
||||
analytics.send(
|
||||
WcAnalyticEvents.TransactionDetailsOpened(
|
||||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
),
|
||||
)
|
||||
stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.Either
|
|||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.router.stack.pushNew
|
||||
import com.domain.blockaid.models.transaction.SimulationResult
|
||||
import com.domain.blockaid.models.transaction.ValidationResult
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
|
|
@ -27,6 +28,7 @@ import com.tangem.domain.tokens.error.CurrencyStatusError
|
|||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message
|
||||
|
|
@ -88,6 +90,7 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
private var wcApproval: WcApproval? = null
|
||||
private var sign: () -> Unit = {}
|
||||
private val feeReloadState = MutableStateFlow(false)
|
||||
private val signatureReceivedAnalyticsSendState = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
@Suppress("UnusedPrivateMember")
|
||||
|
|
@ -121,6 +124,8 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
.collectLatest { (signState, securityCheck) ->
|
||||
if (signingIsDone(signState, useCase)) return@collectLatest
|
||||
|
||||
sendSignatureReceivedAnalytics(useCase, securityCheck)
|
||||
|
||||
this@WcSendTransactionModel.signState = signState
|
||||
val isSecurityCheckContent = securityCheck is Lce.Content
|
||||
val isApprovalMethod = isSecurityCheckContent &&
|
||||
|
|
@ -257,6 +262,12 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun showTransactionRequest() {
|
||||
analytics.send(
|
||||
WcAnalyticEvents.TransactionDetailsOpened(
|
||||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
),
|
||||
)
|
||||
stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo)
|
||||
}
|
||||
|
||||
|
|
@ -358,4 +369,30 @@ internal class WcSendTransactionModel @Inject constructor(
|
|||
private fun copyData(text: String) {
|
||||
clipboardManager.setText(text = text, isSensitive = true)
|
||||
}
|
||||
|
||||
private fun sendSignatureReceivedAnalytics(
|
||||
useCase: WcSignUseCase<*>,
|
||||
securityCheck: Lce<Throwable, BlockAidTransactionCheck.Result>,
|
||||
) {
|
||||
if (signatureReceivedAnalyticsSendState.value) return
|
||||
|
||||
val emulationStatus = when (securityCheck) {
|
||||
is Lce.Content -> when (securityCheck.content.result.simulation) {
|
||||
SimulationResult.FailedToSimulate -> EmulationStatus.CanNotEmulate
|
||||
is SimulationResult.Success -> EmulationStatus.Emulated
|
||||
}
|
||||
is Lce.Error -> EmulationStatus.Error
|
||||
is Lce.Loading -> return
|
||||
}
|
||||
|
||||
analytics.send(
|
||||
WcAnalyticEvents.SignatureRequestReceived(
|
||||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
emulationStatus = emulationStatus,
|
||||
),
|
||||
)
|
||||
|
||||
signatureReceivedAnalyticsSendState.value = true
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import androidx.compose.runtime.Stable
|
|||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.router.stack.pushNew
|
||||
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.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
|
|
@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
|
|
@ -42,6 +45,7 @@ internal class WcSignTransactionModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val useCaseFactory: WcRequestUseCaseFactory,
|
||||
private val signTypedDataUMConverter: WcSignTypedDataUMConverter,
|
||||
private val signTransactionUMConverter: WcSignTransactionUMConverter,
|
||||
|
|
@ -54,9 +58,11 @@ internal class WcSignTransactionModel @Inject constructor(
|
|||
|
||||
val stackNavigation = StackNavigation<WcTransactionRoutes>()
|
||||
|
||||
private var useCase by Delegates.notNull<WcMessageSignUseCase>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
val useCase = useCaseFactory.createUseCase<WcMessageSignUseCase>(params.rawRequest)
|
||||
useCase = useCaseFactory.createUseCase<WcMessageSignUseCase>(params.rawRequest)
|
||||
.onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) }
|
||||
.getOrNull() ?: return@launch
|
||||
useCase.invoke()
|
||||
|
|
@ -109,6 +115,12 @@ internal class WcSignTransactionModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun showTransactionRequest() {
|
||||
analytics.send(
|
||||
WcAnalyticEvents.TransactionDetailsOpened(
|
||||
rawRequest = useCase.rawSdkRequest,
|
||||
network = useCase.network,
|
||||
),
|
||||
)
|
||||
stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
|
|||
onDismiss = {},
|
||||
onSend = {},
|
||||
appInfo = WcTransactionAppInfoContentUM(
|
||||
appName = "React App",
|
||||
appName = "React App React App ReactApp ReactApp ReactApp",
|
||||
appIcon = "",
|
||||
verifiedState = VerifiedDAppState.Verified {},
|
||||
verifiedState = VerifiedDAppState.Unknown,
|
||||
appSubtitle = "react-app.walletconnect.com",
|
||||
),
|
||||
estimatedWalletChanges = WcSendReceiveTransactionCheckResultsUM(
|
||||
|
|
@ -225,7 +225,7 @@ private class WcSendTransactionStateProvider : CollectionPreviewParameterProvide
|
|||
onDismiss = {},
|
||||
onSend = {},
|
||||
appInfo = WcTransactionAppInfoContentUM(
|
||||
appName = "React App",
|
||||
appName = "React App React App React App React App React App",
|
||||
appIcon = "",
|
||||
verifiedState = VerifiedDAppState.Verified {},
|
||||
appSubtitle = "react-app.walletconnect.com",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue