diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 96c968aa66..d34fc1d288 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -235,4 +235,13 @@ internal object TransactionDomainModule { ): GetReverseResolvedEnsAddressUseCase { return GetReverseResolvedEnsAddressUseCase(walletAddressServiceRepository) } + + @Provides + @Singleton + fun sendLargeSolanaTransactionUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + walletManagersFacade: WalletManagersFacade, + ): SendLargeSolanaTransactionUseCase { + return SendLargeSolanaTransactionUseCase(cardSdkConfigRepository, walletManagersFacade) + } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt index dc68d70134..0c76ab802c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheet.kt @@ -49,6 +49,7 @@ inline fun TangemModalBottomSheet( config: TangemBottomSheetConfig, containerColor: Color = TangemTheme.colors.background.primary, skipPartiallyExpanded: Boolean = true, + dismissOnClickOutside: Boolean = true, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit = {}, crossinline content: @Composable ColumnScope.(T) -> Unit, @@ -69,6 +70,7 @@ inline fun TangemModalBottomSheet( containerColor = containerColor, title = title, content = content, + dismissOnClickOutside = dismissOnClickOutside, onBack = onBack, skipPartiallyExpanded = skipPartiallyExpanded, ) @@ -81,12 +83,22 @@ inline fun DefaultModalBottomSheet( config: TangemBottomSheetConfig, containerColor: Color, skipPartiallyExpanded: Boolean = true, + dismissOnClickOutside: Boolean = true, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), ) { var isVisible by remember { mutableStateOf(value = config.isShown) } - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded) + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = skipPartiallyExpanded, + confirmValueChange = { + if (!dismissOnClickOutside) { + it != SheetValue.Hidden // Ignore transitions to hidden (prevents dismiss on outside click/back press) + } else { + true + } + }, + ) if (isVisible && config.content is T) { BasicModalBottomSheet( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt index c1d301135a..2a3d2c624f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.data.walletconnect.network.solana import arrow.core.left +import com.tangem.blockchain.blockchains.solana.SolanaTransactionHelper import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.extensions.encodeBase58 import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -10,10 +11,13 @@ import com.tangem.data.walletconnect.sign.SignCollector import com.tangem.data.walletconnect.sign.SignStateConverter.toResult import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase +import com.tangem.domain.transaction.usecase.SendLargeSolanaTransactionUseCase import com.tangem.domain.walletconnect.error.parseSendError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck +import com.tangem.domain.walletconnect.usecase.method.SignRequirements import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase import dagger.assisted.Assisted @@ -22,18 +26,21 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import okio.ByteString.Companion.decodeBase64 +import timber.log.Timber @Suppress("LongParameterList") internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( override val respondService: WcRespondService, override val analytics: AnalyticsEventHandler, private val prepareAndSign: PrepareAndSignUseCase, + private val sendLargeSolanaTransactionUseCase: SendLargeSolanaTransactionUseCase, @Assisted override val context: WcMethodUseCaseContext, @Assisted override val method: WcSolanaMethod.SignTransaction, blockAidDelegate: BlockAidVerificationDelegate, addressConverter: SolanaBlockAidAddressConverter, ) : BaseWcSignUseCase(), - WcTransactionUseCase { + WcTransactionUseCase, + SignRequirements { override val securityStatus = blockAidDelegate.getSecurityStatus( network = network, @@ -44,15 +51,35 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( ).map { lce -> lce.map { result -> BlockAidTransactionCheck.Result.Plain(result) } } override suspend fun SignCollector.onSign(state: WcSignState) { - val hash = prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network) - .onLeft { error -> - emit(state.toResult(parseSendError(error).left())) - } - .getOrNull() - ?: return - val respond = "{ signature: \"${hash.encodeBase58()}\" }" - val respondResult = respondService.respond(rawSdkRequest, respond) - emit(state.toResult(respondResult)) + val hash = state.signModel.getTxHashFromCompiled() + val formattedHash = getFormattedHash(hash) // uses for flow sendLargeSolanaTransaction + if (context.session.wallet is UserWallet.Cold && isLargeHash(formattedHash)) { + // workaround for large transactions that cannot be signed directly by card + Timber.w("The transaction hash is too large to be signed directly: ${formattedHash.size} bytes") + sendLargeSolanaTransactionUseCase(context.session.wallet as UserWallet.Cold, context.network, formattedHash) + .fold( + ifLeft = { + Timber.e(it.toString()) + emit(state.toResult(parseSendError(it).left())) + }, + ifRight = { + val emptyRespond = ByteArray(0).formatAsSolanaSignature() + val respondResult = respondService.respond(rawSdkRequest, emptyRespond) + emit(state.toResult(respondResult)) + }, + ) + } else { + val signedHash = + prepareAndSign.invoke(transactionData = state.signModel, userWallet = wallet, network = network) + .onLeft { error -> + emit(state.toResult(parseSendError(error).left())) + } + .getOrNull() + ?: return + val respond = signedHash.formatAsSolanaSignature() + val respondResult = respondService.respond(rawSdkRequest, respond) + emit(state.toResult(respondResult)) + } } override fun invoke(): Flow> { @@ -64,6 +91,40 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( return delegate.invoke(transactionData) } + private fun ByteArray.formatAsSolanaSignature(): String { + return "{ signature: \"${this.encodeBase58()}\" }" + } + + private fun TransactionData.getTxHashFromCompiled(): ByteArray { + return when (this) { + is TransactionData.Compiled -> (value as? TransactionData.Compiled.Data.Bytes)?.data + ?: error("Invalid transaction data") + is TransactionData.Uncompiled -> error("Transaction must be compiled") + } + } + + private fun isLargeHash(hash: ByteArray): Boolean { + return hash.size > LARGE_HASH_SIZE + } + + private fun getFormattedHash(hash: ByteArray): ByteArray { + return try { + SolanaTransactionHelper.removeSignaturesPlaceholders(hash) + } catch (e: Exception) { + Timber.e("Failed to format the hash: ${e.message}") + hash + } + } + + override fun isMultipleSignRequired(): Boolean { + val data = method.transaction.decodeBase64()?.toByteArray() ?: ByteArray(0) + return isLargeHash(data) + } + + private companion object { + private const val LARGE_HASH_SIZE = 930 // bytes + } + @AssistedFactory interface Factory { fun create( diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendLargeSolanaTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendLargeSolanaTransactionUseCase.kt new file mode 100644 index 0000000000..819faacd7b --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendLargeSolanaTransactionUseCase.kt @@ -0,0 +1,55 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.blockchains.solana.SolanaWalletManager +import com.tangem.blockchain.extensions.Result +import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.card.models.TwinKey +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.transaction.error.SendTransactionError +import com.tangem.domain.walletmanager.WalletManagersFacade + +/** + * Use case for sending large Solana transactions that cannot be signed directly by the card. + * It handles the transaction creating alt tables and sending the transaction through the Solana network. + * + * @property cardSdkConfigRepository Repository to access card SDK configurations and signers. + * @property walletManagersFacade Facade to manage and retrieve wallet managers. + */ +class SendLargeSolanaTransactionUseCase( + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val walletManagersFacade: WalletManagersFacade, +) { + + suspend operator fun invoke( + userWallet: UserWallet.Cold, + network: Network, + txHash: ByteArray, + ): Either { + val card = userWallet.scanResponse.card + val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins + + val signer = cardSdkConfigRepository.getCommonSigner( + cardId = card.cardId.takeIf { isCardNotBackedUp }, + twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse), + ) + + val walletManager = walletManagersFacade + .getOrCreateWalletManager(userWallet.walletId, network) + ?: error("WalletManager is null") + + if (walletManager !is SolanaWalletManager) return SendTransactionError.UnknownError().left() + val result = walletManager.handleLargeLegacyTransaction(signer, txHash) + return when (result) { + is Result.Failure -> SendTransactionError.BlockchainSdkError( + code = result.error.code, + message = result.error.customMessage, + ).left() + is Result.Success -> Unit.right() + } + } +} \ 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 af7e9c585f..150fd03424 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 @@ -46,4 +46,11 @@ interface WcMutableFee { interface WcApproval { fun getAmount(): WcApprovedAmount? fun updateAmount(amount: WcApprovedAmount?) +} + +/** + * Defines if multiple signatures are required for the transaction + */ +interface SignRequirements { + fun isMultipleSignRequired(): Boolean } \ 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 1ddad75e91..36c5a2b3c1 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 @@ -62,6 +62,8 @@ internal abstract class WcCommonTransactionComponentDelegate( is WcTransactionRoutes.Alert, is WcTransactionRoutes.CustomAllowance, is WcTransactionRoutes.SelectFee, + is WcTransactionRoutes.MultipleTransactions, + WcTransactionRoutes.TransactionProcess, -> stackNavigation?.pop() null -> 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 292804e584..5be85fef22 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,10 +4,13 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.features.send.v2.api.FeeSelectorComponent -import com.tangem.features.send.v2.api.params.FeeSelectorParams +import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeDisplaySource +import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeSelectorDetailsParams 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.components.send.WcSendingProcessComponent +import com.tangem.features.walletconnect.transaction.components.send.WcSendMultipleTransactionsComponent 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 @@ -43,17 +46,32 @@ internal fun getWcCommonScreen( feeSelectorComponentFactory.create( context = appComponentContext, onDismiss = model::popBack, - params = FeeSelectorParams.FeeSelectorDetailsParams( + params = FeeSelectorDetailsParams( state = state.feeSelectorUM, onLoadFee = model::loadFee, feeCryptoCurrencyStatus = model.cryptoCurrencyStatus, cryptoCurrencyStatus = model.cryptoCurrencyStatus, callback = model, feeStateConfiguration = model.feeStateConfiguration, - feeDisplaySource = FeeSelectorParams.FeeDisplaySource.BottomSheet, + feeDisplaySource = FeeDisplaySource.BottomSheet, analyticsCategoryName = WcAnalyticEvents.WC_CATEGORY_NAME, ), ) } + is WcTransactionRoutes.MultipleTransactions -> { + val model = model as? WcSendTransactionModel ?: error("model must be WcSendTransactionModel") + WcSendMultipleTransactionsComponent( + appComponentContext = appComponentContext, + model = model, + onConfirm = config.onConfirm, + ) + } + WcTransactionRoutes.TransactionProcess -> { + val model = model as? WcSendTransactionModel ?: error("model must be WcSendTransactionModel") + WcSendingProcessComponent( + appComponentContext = appComponentContext, + model = model, + ) + } } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendMultipleTransactionsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendMultipleTransactionsComponent.kt new file mode 100644 index 0000000000..0146849910 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendMultipleTransactionsComponent.kt @@ -0,0 +1,33 @@ +package com.tangem.features.walletconnect.transaction.components.send + +import androidx.compose.runtime.Composable +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel +import com.tangem.features.walletconnect.transaction.ui.send.WcSendMultipleTransactionsModalBottomSheet + +internal class WcSendMultipleTransactionsComponent( + private val appComponentContext: AppComponentContext, + private val model: WcSendTransactionModel, + private val onConfirm: () -> Unit, +) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent { + + override fun dismiss() { + model.dismiss() + } + + @Composable + override fun BottomSheet() { + WcSendMultipleTransactionsModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = { model.popBack() }, + content = TangemBottomSheetConfigContent.Empty, + ), + onConfirm = onConfirm, + onBack = { model.popBack() }, + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendingProcessComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendingProcessComponent.kt new file mode 100644 index 0000000000..5723380bbb --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/send/WcSendingProcessComponent.kt @@ -0,0 +1,30 @@ +package com.tangem.features.walletconnect.transaction.components.send + +import androidx.compose.runtime.Composable +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel +import com.tangem.features.walletconnect.transaction.ui.send.WcSendingProcessModalBottomSheet + +internal class WcSendingProcessComponent( + private val appComponentContext: AppComponentContext, + private val model: WcSendTransactionModel, +) : AppComponentContext by appComponentContext, ComposableBottomSheetComponent { + + override fun dismiss() { + model.dismiss() + } + + @Composable + override fun BottomSheet() { + WcSendingProcessModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + ) + } +} \ No newline at end of file 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 8b5afff900..99b80940ee 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 @@ -141,7 +141,13 @@ internal class WcSendTransactionModel @Inject constructor( val isApprovalMethod = isSecurityCheckContent && securityCheck.content is BlockAidTransactionCheck.Result.Approval wcApproval = useCase as? WcApproval - sign = { useCase.sign() } + sign = { + if (isMultipleSignRequired(useCase)) { + openMultipleTransaction(useCase) + } else { + useCase.sign() + } + } buildUiState(securityCheck, useCase, signState, isApprovalMethod) if (feeReloadState.value) { triggerFeeReload() @@ -162,6 +168,17 @@ internal class WcSendTransactionModel @Inject constructor( } } + private fun openMultipleTransaction(useCase: WcSignUseCase<*>) { + stackNavigation.pushNew( + WcTransactionRoutes.MultipleTransactions( + onConfirm = { + useCase.sign() + stackNavigation.pushNew(WcTransactionRoutes.TransactionProcess) + }, + ), + ) + } + /** * Handles callback with updated [feeSelectorUM] from click FeeSelectorComponent. * We need to trigger navigation to dismiss fee selector component @@ -272,6 +289,14 @@ internal class WcSendTransactionModel @Inject constructor( stackNavigation.pop() } + private fun isMultipleSignRequired(useCase: WcSignUseCase<*>): Boolean { + return if (useCase is SignRequirements) { + useCase.isMultipleSignRequired() + } else { + false + } + } + fun showTransactionRequest() { analytics.send( WcAnalyticEvents.TransactionDetailsOpened( 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 843e0e291d..cc1d9513d7 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 @@ -34,6 +34,7 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout val iconType: MessageBottomSheetUMV2.Icon.Type, val iconBgType: MessageBottomSheetUMV2.Icon.BackgroundType, ) : Type() + data class UnknownError( val errorMessage: String?, val onDismiss: () -> Unit, @@ -41,4 +42,12 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout ) : Type() } } + + @Serializable + data class MultipleTransactions( + val onConfirm: () -> Unit, + ) : WcTransactionRoutes() + + @Serializable + data object TransactionProcess : WcTransactionRoutes() } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendMultipleTransactionsModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendMultipleTransactionsModalBottomSheet.kt new file mode 100644 index 0000000000..e8687a8fb3 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendMultipleTransactionsModalBottomSheet.kt @@ -0,0 +1,104 @@ +package com.tangem.features.walletconnect.transaction.ui.send + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.components.* +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.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +@Composable +internal fun WcSendMultipleTransactionsModalBottomSheet( + config: TangemBottomSheetConfig, + onConfirm: () -> Unit, + onBack: () -> Unit, +) { + TangemModalBottomSheet( + config = config, + content = { + Column( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SpacerH24() + Icon( + modifier = Modifier + .size(56.dp) + .clip(RoundedCornerShape(percent = 100)) + .background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f)) + .padding(12.dp), + painter = rememberVectorPainter( + ImageVector.vectorResource(com.tangem.core.ui.R.drawable.ic_alert_24), + ), + tint = TangemTheme.colors.icon.attention, + contentDescription = null, + ) + SpacerH24() + Text( + text = "Multiple Transactions", + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerH8() + Text( + text = "You’ll need to tap your Tangem device a few times to complete this process.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + SpacerH(48.dp) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = "Return", + onClick = onBack, + ) + SpacerH8() + PrimaryButtonIconEnd( + modifier = Modifier.fillMaxWidth(), + iconResId = R.drawable.ic_tangem_24, + text = "Send", + onClick = onConfirm, + ) + } + }, + ) +} + +@Composable +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun WcSendTransactionBottomSheetPreview() { + TangemThemePreview { + WcSendMultipleTransactionsModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + onConfirm = {}, + onBack = {}, + ) + } +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendingProcessModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendingProcessModalBottomSheet.kt new file mode 100644 index 0000000000..17087e2059 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendingProcessModalBottomSheet.kt @@ -0,0 +1,77 @@ +package com.tangem.features.walletconnect.transaction.ui.send + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerH8 +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.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +@Composable +internal fun WcSendingProcessModalBottomSheet(config: TangemBottomSheetConfig) { + TangemModalBottomSheet( + config = config, + onBack = { + // empty to disable back handling + }, + dismissOnClickOutside = false, + content = { + Column( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SpacerH(64.dp) + CircularProgressIndicator( + modifier = Modifier + .size(TangemTheme.dimens.size32), + color = TangemTheme.colors.text.accent, + strokeWidth = TangemTheme.dimens.size2, + ) + SpacerH(32.dp) + Text( + text = "Sending your funds", + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + SpacerH8() + Text( + text = "We’re processing the transaction — it’ll be complete in moments.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + SpacerH(56.dp) + } + }, + ) +} + +@Composable +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO) +@Preview(showBackground = true, device = Devices.PIXEL_7_PRO, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun WcSendTransactionBottomSheetPreview() { + TangemThemePreview { + WcSendingProcessModalBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = {}, + content = TangemBottomSheetConfigContent.Empty, + ), + ) + } +} \ No newline at end of file diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 4829355867..25b1732ddf 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1216" +tangemBlockchainSdk = "develop-1222" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-561" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^