diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/ValidationResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/ValidationResponse.kt index 08e86a0697..2e27aab60b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/ValidationResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/blockaid/models/response/ValidationResponse.kt @@ -7,4 +7,5 @@ import com.squareup.moshi.JsonClass data class ValidationResponse( @Json(name = "status") val status: String, @Json(name = "result_type") val resultType: String, + @Json(name = "description") val description: String?, ) \ No newline at end of file diff --git a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt index 64daa2aaf6..e245687538 100644 --- a/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt +++ b/data/blockaid/src/main/kotlin/com/tangem/data/blockaid/BlockAidMapper.kt @@ -39,6 +39,7 @@ internal object BlockAidMapper { } else { mapSimulationSuccessResult(from.simulation.accountSummary) }, + description = from.validation.description, ) } diff --git a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt index 6d83fc64db..1980d63605 100644 --- a/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt +++ b/data/blockaid/src/test/kotlin/com/tangem/data/blockaid/BlockAidMapperTest.kt @@ -46,7 +46,7 @@ class BlockAidMapperTest { spenders = mapOf("spender" to spenderDetails), ) val response = TransactionScanResponse( - validation = ValidationResponse(status = "Success", resultType = "Benign"), + validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""), simulation = SimulationResponse( status = "Success", accountSummary = AccountSummaryResponse( @@ -79,7 +79,7 @@ class BlockAidMapperTest { outTransfer = listOf(Transfer(value = "1.5", rawValue = "0x2")), ) val response = TransactionScanResponse( - validation = ValidationResponse(status = "Success", resultType = "Benign"), + validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""), simulation = SimulationResponse( status = "Success", accountSummary = AccountSummaryResponse( @@ -105,7 +105,7 @@ class BlockAidMapperTest { @Test fun `when response error validation rhen returns failed to validate`() { val response = TransactionScanResponse( - validation = ValidationResponse(status = "Error", resultType = "Benign"), + validation = ValidationResponse(status = "Error", resultType = "Benign", description = ""), simulation = SimulationResponse( status = "Success", accountSummary = AccountSummaryResponse(emptyList(), emptyList(), null), @@ -120,7 +120,7 @@ class BlockAidMapperTest { @Test fun `when response not benign then returns validation unsafe`() { val response = TransactionScanResponse( - validation = ValidationResponse(status = "Success", resultType = "Phishing"), + validation = ValidationResponse(status = "Success", resultType = "Phishing", description = ""), simulation = SimulationResponse( status = "Success", accountSummary = AccountSummaryResponse(emptyList(), emptyList(), null), @@ -134,7 +134,7 @@ class BlockAidMapperTest { @Test fun `when response simulation not success then returns simulation failed ro simulate`() { val response = TransactionScanResponse( - validation = ValidationResponse(status = "Success", resultType = "Benign"), + validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""), simulation = SimulationResponse( status = "Error", accountSummary = AccountSummaryResponse(emptyList(), emptyList(), null), @@ -148,7 +148,7 @@ class BlockAidMapperTest { @Test fun `when response simulation is empty then returns failed to simulate`() { val txResponse = TransactionScanResponse( - validation = ValidationResponse(status = "Success", resultType = "Benign"), + validation = ValidationResponse(status = "Success", resultType = "Benign", description = ""), simulation = SimulationResponse( status = "Success", accountSummary = AccountSummaryResponse( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt index f5ef109750..9a4e8e8d7c 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSendTransactionUseCase.kt @@ -119,7 +119,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor( emitAll(delegate.invoke(transactionData)) } - override suspend fun getAmount(): Amount? { + override fun getAmount(): Amount? { return approvalAmount } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt index e372f35b8a..2726cb8389 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTransactionUseCase.kt @@ -127,7 +127,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor( return dAppFee } - override suspend fun getAmount(): Amount? { + override fun getAmount(): Amount? { return approvalAmount } diff --git a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/CheckTransactionResult.kt b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/CheckTransactionResult.kt index d7244d6023..6080ac8921 100644 --- a/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/CheckTransactionResult.kt +++ b/domain/blockaid/models/src/main/kotlin/com/domain/blockaid/models/transaction/CheckTransactionResult.kt @@ -8,5 +8,6 @@ package com.domain.blockaid.models.transaction */ data class CheckTransactionResult( val validation: ValidationResult, + val description: String? = null, val simulation: SimulationResult, ) \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt index 53de930b75..d4f126ead7 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcTransactionUseCase.kt @@ -44,6 +44,6 @@ interface WcMutableFee { * [updateAmount] triggered a new [TransactionData] emit */ interface WcApproval { - suspend fun getAmount(): Amount? + fun getAmount(): Amount? fun updateAmount(amount: Amount?) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcCommonTransactionComponentDelegate.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcCommonTransactionComponentDelegate.kt index 0dd02cb797..1be9296325 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcCommonTransactionComponentDelegate.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcCommonTransactionComponentDelegate.kt @@ -60,6 +60,7 @@ internal abstract class WcCommonTransactionComponentDelegate( is WcTransactionRoutes.Transaction, is WcTransactionRoutes.TransactionRequestInfo, is WcTransactionRoutes.Alert, + is WcTransactionRoutes.CustomAllowance, -> stackNavigation?.pop() else -> Unit } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt index d06bc28646..c38c4ce6bd 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt @@ -4,7 +4,9 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.features.walletconnect.connections.components.AlertsComponentV2 import com.tangem.features.walletconnect.connections.utils.WcAlertsFactory.createCommonTransactionAppInfoAlertUM +import com.tangem.features.walletconnect.transaction.components.send.WcCustomAllowanceComponent import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel +import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes internal fun getWcCommonScreen( @@ -23,5 +25,12 @@ internal fun getWcCommonScreen( appComponentContext = appComponentContext, model = model, ) + is WcTransactionRoutes.CustomAllowance -> WcCustomAllowanceComponent( + appComponentContext = appComponentContext, + model = model, + onClickDoneCustomAllowance = { value, isUnlimited -> + (model as? WcSendTransactionModel)?.onClickDoneCustomAllowance(value, isUnlimited) + }, + ) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcCustomAllowanceComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcCustomAllowanceComponent.kt new file mode 100644 index 0000000000..afe90c5207 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcCustomAllowanceComponent.kt @@ -0,0 +1,39 @@ +package com.tangem.features.walletconnect.transaction.components.send + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel +import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM +import com.tangem.features.walletconnect.transaction.ui.approve.WcCustomAllowanceContent +import java.math.BigDecimal + +internal class WcCustomAllowanceComponent( + private val appComponentContext: AppComponentContext, + private val model: WcCommonTransactionModel, + private val onClickDoneCustomAllowance: (BigDecimal, Boolean) -> Unit, +) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent { + + override fun dismiss() { + model.dismiss() + } + + @Composable + override fun BottomSheet() { + val content by model.uiState.collectAsStateWithLifecycle() + val spendAllowance = (content as? WcSendTransactionUM)?.spendAllowance + if (spendAllowance != null) { + WcCustomAllowanceContent( + state = spendAllowance, + onClickDone = { value, isUnlimited -> + router.pop() + onClickDoneCustomAllowance(value, isUnlimited) + }, + onBack = router::pop, + onDismiss = ::dismiss, + ) + } + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt index 648dc13467..2953ccb838 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendTransactionComponent.kt @@ -27,6 +27,7 @@ internal class WcSendTransactionComponent( onClickTransactionRequest = model::showTransactionRequest, onBack = router::pop, onDismiss = ::dismiss, + onClickAllowToSpend = model::onClickAllowToSpend, ) } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/approve/WcSpendAllowanceUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/approve/WcSpendAllowanceUM.kt index 1a7b10dca6..0bdad0e725 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/approve/WcSpendAllowanceUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/approve/WcSpendAllowanceUM.kt @@ -1,9 +1,14 @@ package com.tangem.features.walletconnect.transaction.entity.approve +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.extensions.TextReference +import java.math.BigDecimal internal data class WcSpendAllowanceUM( + val amountValue: BigDecimal, + val isUnlimited: Boolean, val amountText: TextReference, val tokenSymbol: String, val tokenImageUrl: String?, -) \ No newline at end of file + val networkIconRes: Int?, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt index e7bfe5f339..89a9e3c554 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt @@ -1,6 +1,7 @@ package com.tangem.features.walletconnect.transaction.entity.send import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM import com.tangem.features.walletconnect.transaction.entity.blockaid.WcSendReceiveTransactionCheckResultsUM import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionUM import com.tangem.features.walletconnect.transaction.entity.common.WcNetworkInfoUM @@ -10,6 +11,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction internal data class WcSendTransactionUM( val transaction: WcSendTransactionItemUM, override val transactionRequestInfo: WcTransactionRequestInfoUM, + val spendAllowance: WcSpendAllowanceUM? = null, ) : WcCommonTransactionUM internal data class WcSendTransactionItemUM( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 84765ddb0b..4d8311c79c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -24,6 +24,7 @@ import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiv import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList") @@ -46,6 +47,8 @@ internal class WcSendTransactionModel @Inject constructor( val stackNavigation = StackNavigation() + private var wcApproval: WcApproval? = null + init { @Suppress("UnusedPrivateMember") modelScope.launch { @@ -77,6 +80,7 @@ internal class WcSendTransactionModel @Inject constructor( val isSecurityCheckContent = securityCheck is Lce.Content var isApprovalMethod = isSecurityCheckContent && securityCheck.content is BlockAidTransactionCheck.Result.Approval + wcApproval = useCase as? WcApproval buildUiState(securityCheck, useCase, signState) } else -> unknownMethodRunnable() @@ -90,7 +94,9 @@ internal class WcSendTransactionModel @Inject constructor( signState: WcSignState<*>, ) { val blockAidState = when (securityCheck) { - is Lce.Content -> blockAidUiConverter.convert(securityCheck.content.result) + is Lce.Content -> blockAidUiConverter.convert( + WcSendAndReceiveBlockAidUiConverter.Input(securityCheck.content.result, wcApproval?.getAmount()), + ) is Lce.Error -> WcSendReceiveTransactionCheckResultsUM(isLoading = false) is Lce.Loading -> WcSendReceiveTransactionCheckResultsUM(isLoading = true) } @@ -108,6 +114,7 @@ internal class WcSendTransactionModel @Inject constructor( ) as? WcSendTransactionUM transactionUM = transactionUM?.copy( transaction = transactionUM.transaction.copy(estimatedWalletChanges = blockAidState), + spendAllowance = blockAidState.spendAllowance, ) _uiState.emit(transactionUM) } @@ -120,6 +127,17 @@ internal class WcSendTransactionModel @Inject constructor( stackNavigation.pushNew(WcTransactionRoutes.TransactionRequestInfo) } + fun onClickDoneCustomAllowance(value: BigDecimal, isUnlimited: Boolean) { + val maxValue = if (isUnlimited) Double.MAX_VALUE.toBigDecimal() else value + wcApproval?.getAmount()?.let { currentAmount -> + wcApproval?.updateAmount(currentAmount.copy(maxValue = maxValue)) + } + } + + fun onClickAllowToSpend() { + wcApproval?.let { stackNavigation.pushNew(WcTransactionRoutes.CustomAllowance) } + } + private fun showVerifiedAlert(appName: String) { stackNavigation.pushNew(WcTransactionRoutes.Alert(WcTransactionRoutes.Alert.Type.Verified(appName))) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt index bddb365f90..b08fafc300 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt @@ -14,6 +14,9 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout @Serializable data object TransactionRequestInfo : WcTransactionRoutes() + @Serializable + data object CustomAllowance : WcTransactionRoutes() + @Serializable data class Alert(val type: Type) : WcTransactionRoutes() { @Serializable diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcCustomAllowanceContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcCustomAllowanceContent.kt index 08f144454d..ffb2eea5a6 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcCustomAllowanceContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcCustomAllowanceContent.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -23,91 +23,126 @@ import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp import coil.compose.AsyncImage 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.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.walletconnect.impl.R -import com.tangem.features.walletconnect.transaction.entity.approve.WcCustomAllowanceUM +import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM +import java.math.BigDecimal @Composable internal fun WcCustomAllowanceContent( - allowance: WcCustomAllowanceUM, - onAmountChange: (String) -> Unit, - onToggleChange: (Boolean) -> Unit, - onClickDone: () -> Unit, - modifier: Modifier = Modifier, + state: WcSpendAllowanceUM, + onClickDone: (BigDecimal, Boolean) -> Unit, + onBack: () -> Unit, + onDismiss: () -> Unit, ) { - Column( - modifier = modifier - .background(TangemTheme.colors.background.tertiary) - .fillMaxWidth() - .padding(TangemTheme.dimens.size16), - ) { - AmountTextField( - allowance = allowance, - onAmountChange = onAmountChange, - ) + var amountText by remember { mutableStateOf(state.amountValue.toPlainString()) } + var isUnlimited by remember { mutableStateOf(state.isUnlimited) } - Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing16)) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) - .background(color = TangemTheme.colors.background.action) - .padding(TangemTheme.dimens.spacing16) - .fillMaxWidth(), - ) { - Text( - text = stringResourceSafe(R.string.wc_unlimited_amount), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - modifier = Modifier.weight(1f), - ) - TangemSwitch( - checked = allowance.isUnlimited, - onCheckedChange = onToggleChange, - ) - } - - Spacer(modifier = Modifier.height(TangemTheme.dimens.spacing60)) - - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(R.string.common_done), - onClick = onClickDone, - ) + val amount: BigDecimal by remember(amountText) { + mutableStateOf(amountText.toBigDecimalOrNull() ?: state.amountValue) } + + TangemModalBottomSheetWithFooter( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + containerColor = TangemTheme.colors.background.tertiary, + onBack = onBack, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(R.string.wc_custom_allowance_title), + startIconRes = R.drawable.ic_back_24, + onStartClick = onBack, + ) + }, + content = { + Column( + modifier = Modifier + .background(TangemTheme.colors.background.tertiary) + .fillMaxWidth() + .padding(16.dp), + ) { + AmountTextField( + amountText = amountText, + onAmountChange = { amountText = it }, + isEnabled = !isUnlimited, + tokenImageUrl = state.tokenImageUrl, + networkIconRes = state.networkIconRes, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .background(TangemTheme.colors.background.action) + .padding(16.dp) + .fillMaxWidth(), + ) { + Text( + text = stringResourceSafe(R.string.wc_unlimited_amount), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.weight(1f), + ) + TangemSwitch( + checked = isUnlimited, + onCheckedChange = { isUnlimited = it }, + ) + } + + Spacer(modifier = Modifier.height(60.dp)) + } + }, + footer = { + PrimaryButton( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = stringResourceSafe(R.string.common_done), + onClick = { onClickDone(amount, isUnlimited) }, + ) + }, + ) } @Composable private fun AmountTextField( - allowance: WcCustomAllowanceUM, + amountText: String, onAmountChange: (String) -> Unit, + isEnabled: Boolean, + tokenImageUrl: String?, + @DrawableRes networkIconRes: Int?, modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius14)) - .background(color = TangemTheme.colors.background.action) - .padding( - end = TangemTheme.dimens.size8, - top = TangemTheme.dimens.size4, - bottom = TangemTheme.dimens.size4, - ) + .clip(RoundedCornerShape(14.dp)) + .background(TangemTheme.colors.background.action) + .padding(end = 8.dp, top = 4.dp, bottom = 4.dp) .fillMaxWidth(), ) { TextField( - value = allowance.amountText, + value = amountText, onValueChange = onAmountChange, + enabled = isEnabled, label = { Text( text = stringResourceSafe(R.string.send_amount_label), @@ -127,6 +162,9 @@ private fun AmountTextField( unfocusedTextColor = TangemTheme.colors.text.primary1, focusedPlaceholderColor = TangemTheme.colors.text.disabled, unfocusedPlaceholderColor = TangemTheme.colors.text.disabled, + disabledContainerColor = Color.Transparent, + disabledTextColor = TangemTheme.colors.text.disabled, + disabledIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, cursorColor = TangemTheme.colors.icon.primary1, @@ -134,40 +172,44 @@ private fun AmountTextField( ) TokenWithNetworkIcon( - tokenIconUrl = allowance.tokenIconUrl, - networkIconRes = allowance.networkIconRes, + tokenIconUrl = tokenImageUrl, + networkIconRes = networkIconRes, ) } } @Composable private fun TokenWithNetworkIcon( - tokenIconUrl: String, - @DrawableRes networkIconRes: Int, + tokenIconUrl: String?, + @DrawableRes networkIconRes: Int?, modifier: Modifier = Modifier, ) { Box( - modifier = modifier - .size(TangemTheme.dimens.size48), + modifier = modifier.size(48.dp), ) { AsyncImage( + model = tokenIconUrl, + contentDescription = null, modifier = Modifier .aspectRatio(1f) .clip(CircleShape) .align(Alignment.Center) - .padding(TangemTheme.dimens.spacing8), - model = tokenIconUrl, - contentDescription = null, - ) - Image( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .border(TangemTheme.dimens.size2, TangemTheme.colors.background.action, CircleShape) - .padding(TangemTheme.dimens.spacing2), - painter = painterResource(id = networkIconRes), - contentDescription = null, + .padding(8.dp), + placeholder = painterResource(R.drawable.ic_nft_placeholder_20), + error = painterResource(R.drawable.ic_nft_placeholder_20), ) + + if (networkIconRes != null) { + Image( + modifier = Modifier + .align(Alignment.TopEnd) + .size(18.dp) + .border(2.dp, TangemTheme.colors.background.action, CircleShape) + .padding(2.dp), + painter = painterResource(networkIconRes), + contentDescription = null, + ) + } } } @@ -175,10 +217,10 @@ private fun TokenWithNetworkIcon( @Preview(showBackground = true, device = Devices.PIXEL_7_PRO) @Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) internal fun PreviewWcCustomAllowanceContent( - @PreviewParameter(WcCustomAllowanceStateProvider::class) state: WcCustomAllowanceUM, + @PreviewParameter(WcCustomAllowanceStateProvider::class) state: WcSpendAllowanceUM, ) { TangemThemePreview { - TangemModalBottomSheet( + TangemModalBottomSheet( config = TangemBottomSheetConfig( isShown = true, onDismissRequest = {}, @@ -196,22 +238,24 @@ internal fun PreviewWcCustomAllowanceContent( }, content = { WcCustomAllowanceContent( - allowance = state, - onAmountChange = {}, - onToggleChange = {}, - onClickDone = {}, + state = state, + onClickDone = { _, _ -> }, + onBack = {}, + onDismiss = {}, ) }, ) } } -private class WcCustomAllowanceStateProvider : CollectionPreviewParameterProvider( +private class WcCustomAllowanceStateProvider : CollectionPreviewParameterProvider( listOf( - WcCustomAllowanceUM( + WcSpendAllowanceUM( networkIconRes = R.drawable.img_eth_22, - tokenIconUrl = "https://tangem.com", - amountText = "100", + tokenImageUrl = "https://tangem.com", + amountText = TextReference.Str("100"), + amountValue = BigDecimal("100"), + tokenSymbol = "ETH", isUnlimited = false, ), ), diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcSpendAllowanceItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcSpendAllowanceItem.kt index a6de96e855..92ec4f6e17 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcSpendAllowanceItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/approve/WcSpendAllowanceItem.kt @@ -2,6 +2,7 @@ package com.tangem.features.walletconnect.transaction.ui.approve import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -22,13 +23,18 @@ import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllow import com.tangem.features.walletconnect.transaction.ui.common.WcSmallTitleItem @Composable -internal fun WcSpendAllowanceItem(spendAllowance: WcSpendAllowanceUM, modifier: Modifier = Modifier) { +internal fun WcSpendAllowanceItem( + spendAllowance: WcSpendAllowanceUM, + onClickAllowToSpend: () -> Unit, + modifier: Modifier = Modifier, +) { Column( modifier = modifier .clip(RoundedCornerShape(14.dp)) .background(TangemTheme.colors.background.action) .fillMaxWidth() - .padding(end = 12.dp, bottom = 12.dp), + .padding(end = 12.dp, bottom = 12.dp) + .clickable { onClickAllowToSpend() }, ) { WcSmallTitleItem(R.string.wc_allow_to_spend) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt index 6cf15fa757..0ff92d4802 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/TransactionCheckResultsItem.kt @@ -23,7 +23,11 @@ import com.tangem.features.walletconnect.transaction.ui.approve.WcSpendAllowance import kotlinx.collections.immutable.persistentListOf @Composable -internal fun TransactionCheckResultsItem(item: WcSendReceiveTransactionCheckResultsUM, modifier: Modifier = Modifier) { +internal fun TransactionCheckResultsItem( + item: WcSendReceiveTransactionCheckResultsUM, + onClickAllowToSpend: () -> Unit, + modifier: Modifier = Modifier, +) { Column( modifier = modifier .fillMaxWidth(), @@ -38,7 +42,7 @@ internal fun TransactionCheckResultsItem(item: WcSendReceiveTransactionCheckResu if (item.estimatedWalletChanges != null) { WcEstimatedWalletChangesItem(item.estimatedWalletChanges) } else if (item.spendAllowance != null) { - WcSpendAllowanceItem(item.spendAllowance) + WcSpendAllowanceItem(item.spendAllowance, onClickAllowToSpend) } else { WcEstimatedWalletChangesNotLoadedItem() } @@ -57,7 +61,7 @@ private fun TransactionCheckResultsItemPreview( modifier = Modifier .background(TangemTheme.colors.background.tertiary), ) { - TransactionCheckResultsItem(item = item) + TransactionCheckResultsItem(item = item, {}) } } } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt index 9212d3adf1..14d3abede0 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSendAndReceiveBlockAidUiConverter.kt @@ -5,6 +5,7 @@ import com.domain.blockaid.models.transaction.SimulationResult import com.domain.blockaid.models.transaction.ValidationResult import com.domain.blockaid.models.transaction.simultation.AmountInfo import com.domain.blockaid.models.transaction.simultation.SimulationData +import com.tangem.blockchain.common.Amount import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format @@ -21,21 +22,20 @@ import javax.inject.Inject internal const val DECIMALS_AMOUNT = 2 +@Suppress("CyclomaticComplexMethod") internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( private val estimatedWalletChangeUMConverter: WcEstimatedWalletChangeUMConverter, private val spendAllowanceUMConverter: WcSpendAllowanceUMConverter, -) : - Converter { - override fun convert(value: CheckTransactionResult): WcSendReceiveTransactionCheckResultsUM { +) : Converter { + override fun convert(value: Input): WcSendReceiveTransactionCheckResultsUM { + val description = value.result.description return WcSendReceiveTransactionCheckResultsUM( isLoading = false, - notificationText = when (value.validation) { + notificationText = when (value.result.validation) { ValidationResult.SAFE, ValidationResult.FAILED_TO_VALIDATE -> null - // TODO("Add text after approve with designer") - ValidationResult.UNSAFE -> - TextReference.Str("The transaction approves tokens to a known malicious address") + ValidationResult.UNSAFE -> if (!description.isNullOrEmpty()) TextReference.Str(description) else null }, - estimatedWalletChanges = (value.simulation as? SimulationResult.Success)?.data?.let { data -> + estimatedWalletChanges = (value.result.simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { is SimulationData.Approve -> null is SimulationData.SendAndReceive -> { @@ -82,15 +82,21 @@ internal class WcSendAndReceiveBlockAidUiConverter @Inject constructor( } } }, - spendAllowance = (value.simulation as? SimulationResult.Success)?.data?.let { data -> + spendAllowance = (value.result.simulation as? SimulationResult.Success)?.data?.let { data -> when (data) { is SimulationData.SendAndReceive -> null - is SimulationData.Approve -> data.approvedAmounts.map { spendAllowanceUMConverter.convert(it) } - .firstOrNull() + is SimulationData.Approve -> value.approvedAmount?.let { + data.approvedAmounts.map { spendAllowanceUMConverter.convert(it) }.firstOrNull() + } } }, ) } + + data class Input( + val result: CheckTransactionResult, + val approvedAmount: Amount?, + ) } internal fun BigDecimal.amountText() = format { crypto("", DECIMALS_AMOUNT) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSpendAllowanceUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSpendAllowanceUMConverter.kt index 2d9a1a739f..96d797734c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSpendAllowanceUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/blockaid/WcSpendAllowanceUMConverter.kt @@ -2,6 +2,7 @@ package com.tangem.features.walletconnect.transaction.ui.blockaid import com.domain.blockaid.models.transaction.simultation.ApprovedAmount import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.features.walletconnect.impl.R import com.tangem.features.walletconnect.transaction.entity.approve.WcSpendAllowanceUM import com.tangem.utils.converter.Converter @@ -10,12 +11,15 @@ import javax.inject.Inject internal class WcSpendAllowanceUMConverter @Inject constructor() : Converter { override fun convert(value: ApprovedAmount) = WcSpendAllowanceUM( + amountValue = value.approvedAmount, amountText = if (value.isUnlimited) { TextReference.Res(R.string.wc_common_unlimited) } else { TextReference.Str(value.approvedAmount.amountText()) }, + isUnlimited = value.isUnlimited, tokenSymbol = value.tokenInfo.symbol, tokenImageUrl = value.tokenInfo.logoUrl, + networkIconRes = value.tokenInfo.chainId?.toString()?.let { getActiveIconRes(it) }, ) } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index f6a2782b93..70a4e5ec02 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -46,6 +46,7 @@ internal fun WcSendTransactionModalBottomSheet( onClickTransactionRequest: () -> Unit, onBack: () -> Unit, onDismiss: () -> Unit, + onClickAllowToSpend: () -> Unit, ) { TangemModalBottomSheetWithFooter( config = TangemBottomSheetConfig( @@ -92,7 +93,7 @@ internal fun WcSendTransactionModalBottomSheet( } Column(modifier = Modifier.padding(top = 16.dp)) { if (state.estimatedWalletChanges != null) { - TransactionCheckResultsItem(state.estimatedWalletChanges) + TransactionCheckResultsItem(state.estimatedWalletChanges, onClickAllowToSpend) } Spacer(Modifier.height(16.dp)) WcSendTransactionItems( @@ -140,7 +141,7 @@ private fun WcSendTransactionBottomSheetPreview( ) }, content = { - WcSendTransactionModalBottomSheet(state, {}, {}, {}) + WcSendTransactionModalBottomSheet(state, {}, {}, {}, {}) }, ) }