diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ac3eeb6f5d..7fc608245b 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -205,4 +205,4 @@ - \ No newline at end of file + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b536124d5f..1634169316 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,17 @@ plugins { id("configuration") } +//conflict of dependencies when adding WalletConnectV2.0 library +configurations { + all { + exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18") + resolutionStrategy { + force("org.bouncycastle:bcpkix-jdk15on:1.70") + } + exclude(group = "com.github.komputing.kethereum") + } +} + dependencies { implementation(files("libs/walletconnect-1.5.6.aar")) implementation(project(":domain:legacy")) @@ -42,6 +53,7 @@ dependencies { /** AndroidX libraries */ implementation(deps.androidx.core.ktx) + implementation(deps.androidx.core.splashScreen) implementation(deps.androidx.appCompat) implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.constraintLayout) @@ -123,6 +135,8 @@ dependencies { implementation(deps.armadillo) implementation(deps.mviCore.watcher) implementation(deps.kotlin.serialization) + implementation(deps.walletConnectCore) + implementation(deps.walletConnectWeb3) /** Testing libraries */ testImplementation(deps.test.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e632399d0..a9226c6eff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,17 +27,18 @@ + tools:replace="android:allowBackup, android:fullBackupContent"> + @@ -58,40 +60,25 @@ - - - - - - + + + - - - - + + + - + + + - - - + + + + + + + diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index c714678d9c..fad890b2a0 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d +Subproject commit fad890b2a0b552be60d124949ca0a3a4d672dac1 diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 0dae1204d4..e108d35af3 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import by.kirich1409.viewbindingdelegate.viewBinding @@ -31,6 +32,7 @@ import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.welcome.redux.WelcomeAction @@ -90,6 +92,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var walletRouter: WalletRouter + @Inject + lateinit var walletConnectInteractor: WalletConnectInteractor + private var snackbar: Snackbar? = null private val dialogManager = DialogManager() private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) @@ -97,6 +102,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) systemActions() @@ -116,12 +123,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac GooglePayService(createPaymentsClient(this), this), ), ) - store.dispatch( DaggerGraphAction.SetActivityDependencies( testerRouter = testerRouter, scanCardUseCase = scanCardUseCase, walletRouter = walletRouter, + walletConnectInteractor = walletConnectInteractor, ), ) } diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 65d7a0c72c..5de365ee79 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -1,66 +1,60 @@ package com.tangem.tap -import android.app.Application -import android.content.Context -import android.content.pm.PackageManager -import coil.ImageLoader -import coil.ImageLoaderFactory -import com.tangem.Log -import com.tangem.LogFormat -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder -import com.tangem.core.analytics.Analytics -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.data.source.preferences.PreferencesDataSource -import com.tangem.datasource.api.common.MoshiConverter -import com.tangem.datasource.asset.AssetReader -import com.tangem.datasource.config.ConfigManager -import com.tangem.datasource.config.FeaturesLocalLoader -import com.tangem.datasource.config.models.Config -import com.tangem.datasource.connection.NetworkConnectionManager -import com.tangem.domain.DomainLayer -import com.tangem.domain.common.LogConfig -import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles -import com.tangem.tap.common.IntentHandler -import com.tangem.tap.common.analytics.AnalyticsFactory -import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder -import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHandler -import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler -import com.tangem.tap.common.analytics.topup.TopUpController -import com.tangem.tap.common.chat.ChatManager -import com.tangem.tap.common.feedback.AdditionalFeedbackInfo -import com.tangem.tap.common.feedback.FeedbackManager -import com.tangem.tap.common.images.createCoilImageLoader -import com.tangem.tap.common.log.TangemLogCollector -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.appReducer -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.shop.TangemShopService -import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager -import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator -import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation -import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager -import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation -import com.tangem.tap.domain.walletStores.WalletStoresManager -import com.tangem.tap.domain.walletStores.di.provideDefaultImplementation -import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository -import com.tangem.tap.domain.walletStores.repository.WalletManagersRepository -import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository -import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementation +import android.app.* +import android.content.* +import android.content.pm.* +import coil.* +import com.tangem.* +import com.tangem.blockchain.common.* +import com.tangem.blockchain.network.* +import com.tangem.core.analytics.* +import com.tangem.core.featuretoggle.manager.* +import com.tangem.data.source.preferences.* +import com.tangem.datasource.api.common.* +import com.tangem.datasource.asset.* +import com.tangem.datasource.config.* +import com.tangem.datasource.config.models.* +import com.tangem.datasource.connection.* +import com.tangem.domain.* +import com.tangem.domain.common.* +import com.tangem.features.wallet.featuretoggles.* +import com.tangem.tap.common.* +import com.tangem.tap.common.analytics.* +import com.tangem.tap.common.analytics.api.* +import com.tangem.tap.common.analytics.handlers.amplitude.* +import com.tangem.tap.common.analytics.handlers.appsFlyer.* +import com.tangem.tap.common.analytics.handlers.firebase.* +import com.tangem.tap.common.analytics.topup.* +import com.tangem.tap.common.chat.* +import com.tangem.tap.common.feedback.* +import com.tangem.tap.common.images.* +import com.tangem.tap.common.log.* +import com.tangem.tap.common.redux.* +import com.tangem.tap.common.redux.global.* +import com.tangem.tap.common.shop.* +import com.tangem.tap.domain.configurable.warningMessage.* +import com.tangem.tap.domain.tokens.* +import com.tangem.tap.domain.totalBalance.* +import com.tangem.tap.domain.totalBalance.di.* +import com.tangem.tap.domain.walletCurrencies.* +import com.tangem.tap.domain.walletCurrencies.di.* +import com.tangem.tap.domain.walletStores.* +import com.tangem.tap.domain.walletStores.di.* +import com.tangem.tap.domain.walletStores.repository.* +import com.tangem.tap.domain.walletStores.repository.di.* import com.tangem.tap.domain.walletconnect.WalletConnectRepository -import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles -import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.domain.walletconnect2.domain.* +import com.tangem.tap.features.customtoken.api.featuretoggles.* +import com.tangem.tap.proxy.* +import com.tangem.tap.proxy.redux.* import com.tangem.wallet.BuildConfig -import dagger.hilt.android.HiltAndroidApp -import kotlinx.coroutines.launch -import okhttp3.logging.HttpLoggingInterceptor -import org.rekotlin.Store -import timber.log.Timber -import javax.inject.Inject +import dagger.hilt.android.* +import kotlinx.coroutines.* +import okhttp3.logging.* +import org.rekotlin.* +import timber.log.* +import javax.inject.* +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository as WalletConnect2Repository lateinit var store: Store @@ -137,6 +131,12 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var walletFeatureToggles: WalletFeatureToggles + @Inject + lateinit var walletConnect2Repository: WalletConnect2Repository + + @Inject + lateinit var walletConnectSessionsRepository: WalletConnectSessionsRepository + override fun onCreate() { super.onCreate() @@ -151,6 +151,8 @@ class TapApplication : Application(), ImageLoaderFactory { networkConnectionManager = networkConnectionManager, customTokenFeatureToggles = customTokenFeatureToggles, walletFeatureToggles = walletFeatureToggles, + walletConnectRepository = walletConnect2Repository, + walletConnectSessionsRepository = walletConnectSessionsRepository, ), ), ) @@ -193,6 +195,7 @@ class TapApplication : Application(), ImageLoaderFactory { } initTopUpController() + walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId) } private fun initTopUpController() { diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 1147faf13a..e4520b39d6 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -12,7 +12,6 @@ import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.* import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.* import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.features.wallet.ui.dialogs.* @@ -94,14 +93,11 @@ class DialogManager : StoreSubscriber { ChooseNetworkDialog.create(state.dialog.session, state.dialog.networks, context) is WalletConnectDialog.ClipboardOrScanQr -> ClipboardOrScanQrDialog.create(state.dialog.clipboardUri, context) - is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.dialogData, context) + is WalletConnectDialog.RequestTransaction -> TransactionDialog.create(state.dialog.data, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) is WalletConnectDialog.BnbTransactionDialog -> BnbTransactionDialog.create( - data = state.dialog.data, - session = state.dialog.session, - sessionId = state.dialog.sessionId, - dAppName = state.dialog.dAppName, + preparedData = state.dialog.data, context = context, ) is WalletConnectDialog.UnsupportedNetwork -> @@ -110,6 +106,15 @@ class DialogManager : StoreSubscriber { messageRes = R.string.wallet_connect_scanner_error_unsupported_network, context = context, ) + is WalletConnectDialog.SessionProposalDialog -> { + SessionProposalDialog.create( + sessionProposal = state.dialog.sessionProposal, + networks = state.dialog.networks, + context = context, + onApprove = state.dialog.onApprove, + onReject = state.dialog.onReject, + ) + } is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) @@ -118,9 +123,6 @@ class DialogManager : StoreSubscriber { context = context, cardId = state.dialog.cardId, ) - is SaltPayDialog.Activation.NoGas -> NoFundsForActivationDialog.create(context) - is SaltPayDialog.Activation.PutVisaCard -> PutVisaCardDialog.create(context) - is SaltPayDialog.Activation.OnError -> RegistrationErrorDialog.create(context, state.dialog) is WalletDialog.CurrencySelectionDialog -> CurrencySelectionDialog.create(state.dialog, context) is WalletDialog.ChooseTradeActionDialog -> ChooseTradeActionBottomSheetDialog(context, state.dialog) is WalletDialog.SelectAmountToSendDialog -> AmountToSendBottomSheetDialog(context, state.dialog) diff --git a/app/src/main/java/com/tangem/tap/common/FileReader.kt b/app/src/main/java/com/tangem/tap/common/FileReader.kt deleted file mode 100644 index 49e08b5bcb..0000000000 --- a/app/src/main/java/com/tangem/tap/common/FileReader.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.tap.common - -import android.content.Context -import com.tangem.tap.common.extensions.readFile -import com.tangem.tap.common.extensions.rewriteFile - -interface FileReader { - fun readFile(fileName: String): String - fun rewriteFile(content: String, fileName: String) -} - -class AndroidFileReader(private val context: Context) : FileReader { - override fun readFile(fileName: String): String { - return context.readFile(fileName) - } - - override fun rewriteFile(content: String, fileName: String) { - context.rewriteFile(content, fileName) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt index 61d71c5a14..0a7928efd5 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/ParamCardCurrencyConverter.kt @@ -3,7 +3,6 @@ package com.tangem.tap.common.analytics.converters import com.tangem.blockchain.common.Blockchain import com.tangem.common.Converter import com.tangem.domain.common.CardTypesResolver -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.analytics.events.AnalyticsParam /** @@ -17,7 +16,6 @@ class ParamCardCurrencyConverter : Converter AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.isTangemTwins() -> AnalyticsParam.CurrencyType.Blockchain(Blockchain.Bitcoin) - value.isSaltPay() -> AnalyticsParam.CurrencyType.Token(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay)) value.getBlockchain() != Blockchain.Unknown -> AnalyticsParam.CurrencyType.Blockchain(value.getBlockchain()) value.getPrimaryToken() != null -> AnalyticsParam.CurrencyType.Token(value.getPrimaryToken()!!) else -> null diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 15c29ad5d0..7a788fd5c6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -87,12 +87,54 @@ sealed class AnalyticsParam { } sealed class TxSentFrom(val value: String) { - object Send : TxSentFrom("Send") - object Swap : TxSentFrom("Swap") + data class Send( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + ) : TxSentFrom("Send"), TxData + + data class Swap( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + ) : TxSentFrom("Swap"), TxData + + data class Approve( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + val permissionType: String, + ) : TxSentFrom("Approve"), TxData + object WalletConnect : TxSentFrom("WalletConnect") object Sell : TxSentFrom("Sell") } + sealed interface TxData { + val blockchain: String + val token: String + val feeType: FeeType + } + + sealed class FeeType(val value: String) { + object Fixed : FeeType("Fixed") + object Min : FeeType("Min") + object Normal : FeeType("Normal") + object Max : FeeType("Max") + + companion object { + fun fromString(feeType: String): FeeType { + return when (feeType) { + Min.value -> Min + Normal.value -> Normal + Max.value -> Max + Fixed.value -> Fixed + else -> Fixed + } + } + } + } + sealed class WalletCreationType(val value: String) { object PrivateKey : WalletCreationType("Private key") object NewSeed : WalletCreationType("New seed") @@ -100,9 +142,13 @@ sealed class AnalyticsParam { } companion object Key { + const val BLOCKCHAIN = "blockchain" + const val TOKEN = "Token" const val SOURCE = "Source" const val BALANCE = "Balance" const val BATCH = "Batch" + const val FEE_TYPE = "Fee Type" + const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" const val FIRMWARE = "Firmware" const val CURRENCY = "Currency" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index 4f63bd4961..f4ef3e133f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -49,10 +49,25 @@ sealed class Basic( params = mapOf(AnalyticsParam.CURRENCY to currency.value), ) - class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom) : Basic( + class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom, memoType: MemoType) : Basic( event = "Transaction sent", - params = mapOf(AnalyticsParam.SOURCE to sentFrom.value), - ) + params = buildMap { + this[AnalyticsParam.SOURCE] = sentFrom.value + if (sentFrom is AnalyticsParam.TxData) { + this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain + this[AnalyticsParam.TOKEN] = sentFrom.token + this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value + } + if (sentFrom is AnalyticsParam.TxSentFrom.Approve) { + this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType + } + this["Memo"] = memoType.name + }, + ) { + enum class MemoType { + Empty, Full, Null + } + } class ScanError(error: Throwable) : Basic( event = "Scan", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 46e02683ae..837c9bce3c 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -73,16 +73,36 @@ sealed class Token( error: Throwable? = null, ) : Token("Token / Send", event, params, error) { - class ScreenOpened : Send("Send Screen Opened") - class ButtonPaste : Send("Button - Paste") - class ButtonQRCode : Send("Button - QR Code") - class ButtonSwapCurrency : Send("Button - Swap Currency") + class ScreenOpened : Send(event = "Send Screen Opened") + class ButtonPaste : Send(event = "Button - Paste") + class ButtonQRCode : Send(event = "Button - QR Code") + class ButtonSwapCurrency : Send(event = "Button - Swap Currency") - class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send( - event = "Transaction Sent", - params = mapOf("Token" to type.value), - error = error, - ) + class AddressEntered(sourceType: SourceType, validationResult: ValidationResult) : Send( + event = "Address Entered", + params = mapOf( + "Source" to sourceType.name, + "Validation" to validationResult.name, + ), + ) { + enum class SourceType { + QRCode, PasteButton, PastePopup + } + + enum class ValidationResult { + Success, Fail + } + } + + class SelectedCurrency(currency: CurrencyType) : Send( + event = "Selected Currency", + params = mapOf("Type" to currency.value), + ) { + + enum class CurrencyType(val value: String) { + Token(value = "Token"), AppCurrency(value = "App Currency") + } + } } sealed class Topup( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 748a82a14e..864c9dadbb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -45,11 +45,6 @@ class CardContextInterceptor( ProductType.Note -> "Note" ProductType.Twins -> "Twin" ProductType.Wallet -> "Wallet" - ProductType.SaltPay -> if (scanResponse.cardTypesResolver.isSaltPayVisa()) { - "Visa" - } else { - "Visa Backup" - } ProductType.Start2Coin -> "Start2Coin" else -> if (DemoHelper.isDemoCard(scanResponse)) { if (DemoHelper.isTestDemoCard(scanResponse)) { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt b/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt deleted file mode 100644 index 140f0946b4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/BackupService.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.common.extensions - -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.operations.backup.BackupService - -/** -[REDACTED_AUTHOR] - */ -@Suppress("MagicNumber") -fun BackupService.primaryCardIsSaltPayVisa(): Boolean { - return primaryCardId?.slice(0..3)?.let(SaltPayWorkaround::isVisaBatchId) ?: false -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt index db544c0297..8cdf3302b7 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Context.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Context.kt @@ -1,20 +1,11 @@ package com.tangem.tap.common.extensions -import android.content.ContentResolver -import android.content.Context -import android.content.pm.PackageManager -import android.content.res.Resources -import android.net.Uri -import androidx.annotation.AnyRes -import androidx.core.content.ContextCompat - -fun Context.readFile(fileName: String): String = this.openFileInput(fileName).bufferedReader().readText() - -fun Context.rewriteFile(content: String, fileName: String) { - this.openFileOutput(fileName, Context.MODE_PRIVATE).use { - it.write(content.toByteArray(), 0, content.length) - } -} +import android.content.* +import android.content.pm.* +import android.content.res.* +import android.net.* +import androidx.annotation.* +import androidx.core.content.* fun Context.isPermissionGranted(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt index 9da1448d9c..2eb87310f9 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/ImageRequest.kt @@ -1,32 +1,10 @@ package com.tangem.tap.common.extensions -import android.widget.ImageView -import coil.ImageLoader -import coil.imageLoader -import coil.load -import coil.request.Disposable import coil.request.ImageRequest -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ fun ImageRequest.Builder.cardImageData(any: Any?): ImageRequest.Builder = apply { - data(any?.parseData()) -} - -fun ImageView.loadCardImage( - data: Any?, - imageLoader: ImageLoader = context.imageLoader, - builder: ImageRequest.Builder.() -> Unit = {}, -): Disposable = this.load( - data = data?.parseData(), - imageLoader = imageLoader, - builder = builder, -) - -private fun Any?.parseData(): Any? = when { - this is String && this == Artwork.SALT_PAY_URL -> R.drawable.img_salt_pay_visa - else -> this + data(any) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 925cb0c728..36dfe253b5 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -2,9 +2,6 @@ package com.tangem.tap.common.extensions import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionHistoryProvider import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.common.services.Result @@ -38,9 +35,6 @@ suspend fun WalletManager.safeUpdate(): Result = try { Result.Success(wallet) } else { update() - if (wallet.blockchain == Blockchain.SaltPay && this is TransactionHistoryProvider) { - this.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens()) - } Result.Success(wallet) } } catch (exception: Exception) { @@ -87,26 +81,10 @@ fun WalletManager?.getAddressData(): WalletDataModel.AddressData? { return if (addressDataList.isEmpty()) null else addressDataList[0] } -fun WalletManager.getBlockchainTxHistory(): List = wallet.getBlockchainTxHistory() - -fun WalletManager.getTokenTxHistory(token: Token): List = wallet.getTokenTxHistory(token) - fun WalletManager.Companion.stub(): T { val wallet = Wallet(Blockchain.Unknown, setOf(), Wallet.PublicKey(byteArrayOf(), null, null), setOf()) return object : WalletManager(wallet) { override val currentHost: String = "" override suspend fun update() {} } as T -} - -fun Wallet.getBlockchainTxHistory(): List { - return historyTransactions.filter { - it.contractAddress.isNullOrEmpty() - } -} - -fun Wallet.getTokenTxHistory(token: Token): List { - return historyTransactions.filter { - it.contractAddress == token.contractAddress - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index de6bae2411..4ebc39d1d2 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -24,7 +24,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware @@ -94,7 +93,6 @@ data class AppState( HomeMiddleware.handler, OnboardingNoteMiddleware.handler, OnboardingWalletMiddleware.handler, - OnboardingSaltPayMiddleware.handler, OnboardingOtherCardsMiddleware.handler, WalletMiddleware().walletMiddleware, TwinCardsMiddleware.handler, diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index ab957b81b1..5127aae02f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -20,6 +20,18 @@ sealed class AppDialog : StateDialog { val onOk: VoidCallback? = null, ) : AppDialog() + data class OkCancelDialogRes( + val headerId: Int, + val messageId: Int, + val okButton: DialogButton, + val cancelButton: DialogButton, + ) : AppDialog() + + data class DialogButton( + val title: Int, + val action: VoidCallback? = null, + ) + object ScanFailsDialog : AppDialog() data class AddressInfoDialog( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index f589ca6743..d599f9c378 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -38,19 +38,16 @@ sealed class GlobalAction : Action { sealed class Onboarding : GlobalAction() { /** * Initiate an onboarding process. - * For resuming unfinished backup of standard Wallet and SaltPay cards see + * For resuming unfinished backup of standard Wallet see * BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup */ data class Start(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : Onboarding() /** - * Initiate resuming of unfinished backup for standard Wallet and SaltPay cards. + * Initiate resuming of unfinished backup for standard Wallet. * See more BackupAction.CheckForUnfinishedBackup */ - data class StartForUnfinishedBackup( - val addedBackupCardsCount: Int, - val isSaltPayVisa: Boolean, - ) : Onboarding() + data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding() object Stop : Onboarding() } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index 01d00c9a82..46835f3076 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -6,9 +6,7 @@ import com.tangem.common.extensions.guard import com.tangem.datasource.config.models.Config import com.tangem.domain.common.LogConfig import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.entities.FiatCurrency import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -26,16 +24,12 @@ import com.tangem.tap.network.exchangeServices.ExchangeService import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoEnvironment import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService -import com.tangem.tap.network.exchangeServices.utorg.UtorgAuthProvider -import com.tangem.tap.network.exchangeServices.utorg.UtorgEnvironment -import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import com.tangem.tap.userTokensRepository import com.tangem.tap.walletCurrenciesManager -import com.tangem.wallet.BuildConfig import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.DispatchFunction @@ -109,13 +103,8 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di return } - val scanResponse = globalState.scanResponse ?: globalState.onboardingState.onboardingManager?.scanResponse - // if config not set -> try to get it based on a scanResponse.productType - val unsafeChatConfig = action.chatConfig ?: when { - scanResponse?.cardTypesResolver?.isSaltPay() == true -> config.saltPayConfig?.sprinklr - else -> config.zendesk - } + val unsafeChatConfig = action.chatConfig ?: config.zendesk val chatConfig = unsafeChatConfig.guard { store.dispatchDebugErrorNotification("ZendeskConfig not initialized") @@ -139,11 +128,10 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di store.state.globalState.scanResponse ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse } - val productTypeProvider: () -> ProductType? = { scanResponseProvider.invoke()?.productType } val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card } val exchangeManager = CurrencyExchangeManager( - buyService = makeBuyExchangeService(config, productTypeProvider), + buyService = makeBuyExchangeService(config), sellService = makeSellExchangeService(config), primaryRules = CardExchangeRules(cardProvider), ) @@ -213,28 +201,13 @@ private fun makeSellExchangeService(config: Config): ExchangeService { ) } -private fun makeBuyExchangeService(config: Config, productTypeProvider: () -> ProductType?): ExchangeService { +private fun makeBuyExchangeService(config: Config): ExchangeService { return BuyExchangeService( - productTypeProvider = productTypeProvider, mercuryoService = makeMercuryoExchangeService(config), - utorgService = makeUtorgExchangeService(config), ) } private fun makeMercuryoExchangeService(config: Config): MercuryoService { val mercuryoEnvironment = MercuryoEnvironment.prod(config.mercuryoWidgetId, config.mercuryoSecret) return MercuryoService(mercuryoEnvironment) -} - -private fun makeUtorgExchangeService(config: Config): UtorgExchangeService { - val saltPayConfig = requireNotNull(config.saltPayConfig) - - val utorgAuthProvider = UtorgAuthProvider(saltPayConfig.kycProvider.sidValue) - val utorgEnvironment = if (BuildConfig.DEBUG) { - UtorgEnvironment.stage(utorgAuthProvider, LogConfig.network.utorgService) - // UtorgEnvironment.mock() - } else { - UtorgEnvironment.prod(utorgAuthProvider) - } - return UtorgExchangeService(utorgEnvironment) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index ffb1ece346..c395569c5c 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -229,7 +229,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co tangemSdk.config.cardIdDisplayFormat = when { scanResponse == null -> CardIdDisplayFormat.Full scanResponse.cardTypesResolver.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4) - scanResponse.cardTypesResolver.isSaltPay() -> CardIdDisplayFormat.None else -> CardIdDisplayFormat.Full } } diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index b0ee231286..fc7bdedd39 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -22,6 +22,10 @@ sealed class TapError( object UnknownError : TapError(R.string.send_error_unknown) open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage)) + data class UnsupportedState( + val stateError: String, + ) : TapError(R.string.common_custom_string, listOf("Unsupported state: $stateError")) + object ScanCardError : TapError(R.string.scan_card_error) object UnknownBlockchain : TapError(R.string.wallet_error_unsupported_blockchain_subtitle) object NoInternetConnection : TapError(R.string.wallet_notification_no_internet) @@ -38,11 +42,6 @@ sealed class TapError( data class DustAmount(override val args: List) : TapError(R.string.send_error_dust_amount_format) object DustChange : TapError(R.string.send_error_dust_change) - data class UnsupportedState( - val stateError: String, - val customMessage: String = "Unsupported state:", - ) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError")) - sealed class WalletManager { object CreationError : CustomError("Can't create wallet manager") class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount) diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 54e70bd879..50182bde55 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -1,10 +1,6 @@ package com.tangem.tap.domain -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.* import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics @@ -13,6 +9,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.attestation.Attestation +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain @@ -25,10 +22,6 @@ import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.walletStoresManager import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.CoroutineScope @@ -85,10 +78,24 @@ class TapWalletManager( store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed)) store.dispatch(WalletAction.Warnings.CheckIfNeeded) } - + setupWalletConnectV2(userWallet) loadData(userWallet, refresh) } + private fun setupWalletConnectV2(userWallet: UserWallet) { + val cardId = if (userWallet.cardsInWallet.size == 1) { + userWallet.cardId + } else { + null + } + scope.launch { + store.state.daggerGraphState.walletConnectInteractor?.startListening( + userWalletId = userWallet.walletId.stringValue, + cardId = cardId, + ) + } + } + suspend fun loadData(userWallet: UserWallet, refresh: Boolean = false) { walletStoresManager.fetch(userWallet, refresh) .doOnSuccess { diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 19bbc1ae52..f963c6f982 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -4,7 +4,6 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber import com.tangem.operations.attestation.CardVerifyAndGetInfo @@ -21,7 +20,7 @@ val CardDTO.isFirmwareMultiwalletAllowed: Boolean get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1 val CardDTO.isHdWalletAllowedByApp: Boolean - get() = settings.isHDWalletAllowed && !isSaltPay + get() = settings.isHDWalletAllowed @Suppress("UnnecessaryParentheses") fun CardDTO.hasSignedHashes(): Boolean { diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt index fe751acee5..fc0acca07c 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.address.AddressType import com.tangem.tap.domain.model.WalletDataModel.AddressData import com.tangem.tap.domain.model.WalletDataModel.Status @@ -28,7 +27,6 @@ data class WalletDataModel( val fiatRate: BigDecimal?, val isCardSingleToken: Boolean, val isCustom: Boolean, - val historyTransactions: List?, ) { data class WalletAddresses( @@ -79,6 +77,7 @@ data class WalletDataModel( data class Unreachable( override val errorMessage: String?, + override val amount: BigDecimal = BigDecimal.ZERO, ) : Status() { override val isErrorStatus: Boolean = true } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 3cc1d2e5f5..d4e6cbd1fc 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -21,7 +21,6 @@ class UserWalletBuilder( get() = when (productType) { ProductType.Note -> "Note" ProductType.Twins -> "Twin" - ProductType.SaltPay -> "SaltPay" ProductType.Start2Coin -> "Start2Coin" ProductType.Wallet -> when { card.isBackupNotAllowed -> "Tangem card" diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt index 68626b34eb..64f5f8156d 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -60,7 +60,6 @@ class UserWalletIdBuilder private constructor( ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() ProductType.Note, ProductType.Wallet, - ProductType.SaltPay, ProductType.Start2Coin, -> null }, diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt index ecca6f2314..e7d01ecdb6 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt @@ -9,8 +9,6 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.tap.common.extensions.getBlockchainTxHistory -import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel @@ -114,7 +112,6 @@ private fun BlockchainNetwork.getBlockchainWalletData( fiatRate = null, isCardSingleToken = false, isCustom = currency.isCustomCurrency(cardDerivationStyle), - historyTransactions = walletManager?.getBlockchainTxHistory(), ) } @@ -138,7 +135,6 @@ private fun BlockchainNetwork.getTokensWalletsData( fiatRate = null, isCardSingleToken = token == primaryToken, isCustom = currency.isCustomCurrency(cardDerivationStyle), - historyTransactions = walletManager?.getTokenTxHistory(token), ) } } @@ -156,7 +152,6 @@ private fun Blockchain.toBlockchainWalletData(walletManager: WalletManager): Wal fiatRate = null, isCardSingleToken = false, isCustom = false, - historyTransactions = walletManager.getBlockchainTxHistory(), ) } @@ -174,7 +169,6 @@ private fun Token.toTokenWalletData(walletManager: WalletManager, primaryToken: fiatRate = null, isCardSingleToken = this == primaryToken, isCustom = false, - historyTransactions = walletManager.getTokenTxHistory(this), ) } diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt index 7c665e5cdd..efca92a6e8 100644 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/moduleMessage/ModuleMessageConverter.kt @@ -5,8 +5,6 @@ import com.tangem.common.module.ModuleMessage import com.tangem.common.module.ModuleMessageConverter import com.tangem.domain.DomainModuleMessage import com.tangem.tap.domain.moduleMessage.domain.DomainMessageConverter -import com.tangem.tap.domain.moduleMessage.saltPay.SaltPayMessageConverter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage class ModuleMessageConverter( private val context: Context, @@ -15,7 +13,6 @@ class ModuleMessageConverter( override fun convert(message: ModuleMessage): ConvertedMessage { val convertedMessage = when (message) { is DomainModuleMessage -> DomainMessageConverter(context).convert(message) - is SaltPayModuleMessage -> SaltPayMessageConverter(context).convert(message) else -> null } return convertedMessage ?: convertUnknownMessage(message) diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt deleted file mode 100644 index 9db1c78996..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/SaltPayMessageConverter.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem.tap.domain.moduleMessage.saltPay - -import android.content.Context -import com.tangem.common.module.ModuleMessageConverter -import com.tangem.tap.domain.moduleMessage.ConvertedMessage -import com.tangem.tap.domain.moduleMessage.saltPay.converter.SaltPayActivationErrorConverter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayModuleMessage - -/** -[REDACTED_AUTHOR] - */ -class SaltPayMessageConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayModuleMessage): ConvertedMessage? { - return when (message) { - is SaltPayModuleError -> SaltPayErrorConverter(context).convert(message) - else -> null - } - } -} - -class SaltPayErrorConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayModuleError): ConvertedMessage? = when (message) { - is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message) - else -> null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt b/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt deleted file mode 100644 index 1771a2db47..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/moduleMessage/saltPay/converter/SaltPayActivationErrorConverter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tap.domain.moduleMessage.saltPay.converter - -import android.content.Context -import com.tangem.common.module.ModuleMessageConverter -import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -internal class SaltPayActivationErrorConverter( - private val context: Context, -) : ModuleMessageConverter { - - override fun convert(message: SaltPayActivationError): ConvertedDialogMessage? { - val saltPayError = message as? SaltPayActivationError ?: throw UnsupportedOperationException() - - val dialogMessage = when (saltPayError) { - SaltPayActivationError.NoGas -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_no_gas_title), - message = context.getString(R.string.saltpay_error_no_gas_message), - ) - } - SaltPayActivationError.EmptyBackupCardScanned -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_empty_backup_title), - message = context.getString(R.string.saltpay_error_empty_backup_message), - ) - } - SaltPayActivationError.WeakPin -> { - ConvertedDialogMessage( - title = context.getString(R.string.saltpay_error_pin_weak_title), - message = context.getString(R.string.saltpay_error_pin_weak_message), - ) - } - else -> { - val errorMessage = if (saltPayError.message == SaltPayActivationError.EMPTY_MESSAGE) { - saltPayError::class.java.simpleName - } else { - saltPayError.message - } - ConvertedDialogMessage( - title = context.getString(R.string.common_error), - message = errorMessage, - ) - } - } - - return dialogMessage - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index e5a3579e07..f885b4fa13 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -5,20 +5,14 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService import com.tangem.tap.* import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor -import com.tangem.tap.common.extensions.addContext -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa -import com.tangem.tap.common.extensions.setContext +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -26,17 +20,11 @@ import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.disclaimer.redux.DisclaimerAction import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.rekotlin.Action internal object LegacyScanProcessor { @@ -85,24 +73,16 @@ internal object LegacyScanProcessor { onScanStateChange(false) sendAnalytics(analyticsEvent, scanResponse) - checkForUnfinishedBackupForSaltPay( - backupService = backupService, + showDisclaimerIfNeed( scanResponse = scanResponse, + disclaimerWillShow = disclaimerWillShow, onFailure = onFailure, - nextHandler = { scanResponse1 -> - showDisclaimerIfNeed( - scanResponse = scanResponse1, - disclaimerWillShow = disclaimerWillShow, - onFailure = onFailure, - nextHandler = { scanResponse2 -> - onScanSuccess( - scanResponse = scanResponse2, - onProgressStateChange = onProgressStateChange, - onSuccess = onSuccess, - onWalletNotCreated = onWalletNotCreated, - onFailure = onFailure, - ) - }, + nextHandler = { scanResponse2 -> + onScanSuccess( + scanResponse = scanResponse2, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, ) }, ) @@ -121,35 +101,6 @@ internal object LegacyScanProcessor { } } - /** - * It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards - * see BackupAction.CheckForUnfinishedBackup - * If user touches card other than Visa SaltPay - show dialog and block next processing - */ - private suspend inline fun checkForUnfinishedBackupForSaltPay( - backupService: BackupService, - scanResponse: ScanResponse, - nextHandler: (ScanResponse) -> Unit, - onFailure: suspend (error: TangemError) -> Unit, - ) { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - nextHandler(scanResponse) - return - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == scanResponse.card.cardId } - ?: false - - if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - nextHandler(scanResponse) - } - } - private suspend inline fun showDisclaimerIfNeed( scanResponse: ScanResponse, crossinline disclaimerWillShow: () -> Unit = {}, @@ -165,7 +116,7 @@ internal object LegacyScanProcessor { scope.launch { delay(DELAY_SDK_DIALOG_CLOSE) disclaimerWillShow() - arrayOf( + store.dispatchWithMain( DisclaimerAction.Show( fromScreen = AppScreen.Home, callback = DisclaimerCallback( @@ -192,7 +143,6 @@ internal object LegacyScanProcessor { crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline onWalletNotCreated: suspend () -> Unit, crossinline onSuccess: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, ) { val globalState = store.state.globalState val tapWalletManager = globalState.tapWalletManager @@ -200,66 +150,21 @@ internal object LegacyScanProcessor { store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - val cardTypesResolver = scanResponse.cardTypesResolver - if (cardTypesResolver.isSaltPay()) { - if (cardTypesResolver.isSaltPayVisa()) { - val manager = SaltPayActivationManagerFactory( - blockchain = scanResponse.cardTypesResolver.getBlockchain(), - card = scanResponse.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager) - delay(500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(scanResponse) - val isOnboardingCase = result.data - if (isOnboardingCase) { - onWalletNotCreated() - store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false)) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - onFailure(TangemSdkError.ExceptionError(result.error)) - } - } - } + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) + onWalletNotCreated() + store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + Analytics.setContext(scanResponse) + if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { + onWalletNotCreated() + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } } else { delay(DELAY_SDK_DIALOG_CLOSE) - if (scanResponse.card.backupStatus?.isActive == false) { - val error = SaltPayActivationError.PutVisaCard - SaltPayExceptionHandler.handle(error) - onFailure(TangemSdkError.ExceptionError(error)) - } else { - Analytics.setContext(scanResponse) - onSuccess(scanResponse) - } - } - } else { - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - Analytics.addContext(scanResponse) - onWalletNotCreated() - store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true)) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) { onProgressStateChange(it) } - } else { - Analytics.setContext(scanResponse) - if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) { - onWalletNotCreated() - store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) - navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } + onSuccess(scanResponse) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 52d6cf4339..45a463343a 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -7,7 +7,6 @@ import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.card.ScanCardException import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.backupService import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -52,7 +51,6 @@ internal object UseCaseScanProcessor { if (analyticsEvent != null) { add(AnalyticsChain(analyticsEvent)) } - add(CheckForUnfinishedSaltPayBackupChain(backupService)) add(DisclaimerChain(store, disclaimerWillShow)) add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage)) } @@ -91,9 +89,7 @@ internal object UseCaseScanProcessor { navigateTo(exception.onboardingRoute) onWalletNotCreated() } - is ScanChainException.PutSaltPayVisaCard, is ScanChainException.DisclaimerWasCanceled, - is ScanChainException.CheckForSaltPayOnboardingCaseException, -> onFailure(scanCardExceptionConverter.convertBack(exception)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index 21d0e75290..ce46f2f527 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -3,12 +3,9 @@ package com.tangem.tap.domain.scanCard.chains import arrow.core.Either import arrow.core.left import arrow.core.right -import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.ScanCardException -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.core.chain.Chain import com.tangem.domain.models.scan.ScanResponse @@ -21,13 +18,8 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.domain.TapWalletManager import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import kotlinx.coroutines.delay import org.rekotlin.Store @@ -35,9 +27,7 @@ import org.rekotlin.Store * Handles the verification process to determine if the scanned card requires onboarding. * * Returns: - * - [ScanChainException.OnboardingNeeded] if onboarding required for none SaltPay cards. - * - [ScanChainException.CheckForSaltPayOnboardingCaseException] if unable to check for SaltPay onboarding case. - * - [ScanChainException.PutSaltPayVisaCard] if scanned SaltPay card is not activated and onboarding required. + * - [ScanChainException.OnboardingNeeded] if onboarding required. * * @param store the [Store] that holds the state of the app. * @param tapWalletManager manager responsible for handling operations related to the Wallet. @@ -54,54 +44,10 @@ class CheckForOnboardingChain( override suspend fun invoke( previousChainResult: ScanResponse, ): Either { - val cardTypesResolver = previousChainResult.cardTypesResolver - tapWalletManager.updateConfigManager(previousChainResult) store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult)) return when { - cardTypesResolver.isSaltPayVisa() -> { - val manager = SaltPayActivationManagerFactory( - blockchain = previousChainResult.cardTypesResolver.getBlockchain(), - card = previousChainResult.card, - ).create() - val result = OnboardingSaltPayHelper.isOnboardingCase(previousChainResult, manager) - delay(timeMillis = 500) - withMainContext { - when (result) { - is Result.Success -> { - Analytics.addContext(previousChainResult) - val isOnboardingCase = result.data - if (isOnboardingCase) { - store.dispatch( - GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = false), - ) - store.dispatch(OnboardingSaltPayAction.SetDependencies(manager)) - store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false)) - ScanChainException.OnboardingNeeded(AppScreen.OnboardingWallet).left() - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - previousChainResult.right() - } - } - is Result.Failure -> { - delay(DELAY_SDK_DIALOG_CLOSE) - SaltPayExceptionHandler.handle(result.error) - ScanChainException.CheckForSaltPayOnboardingCaseException(result.error).left() - } - } - } - } - cardTypesResolver.isSaltPayWallet() -> { - delay(DELAY_SDK_DIALOG_CLOSE) - if (previousChainResult.card.backupStatus?.isActive == false) { - SaltPayExceptionHandler.handle(SaltPayActivationError.PutVisaCard) - ScanChainException.PutSaltPayVisaCard.left() - } else { - Analytics.setContext(previousChainResult) - previousChainResult.right() - } - } OnboardingHelper.isOnboardingCase(previousChainResult) -> { Analytics.addContext(previousChainResult) store.dispatchOnMain(GlobalAction.Onboarding.Start(previousChainResult, canSkipBackup = true)) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt deleted file mode 100644 index f01f9f76d7..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForUnfinishedSaltPayBackupChain.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.tap.domain.scanCard.chains - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.domain.card.ScanCardException -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.core.chain.Chain -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa - -/** - * Handles the verification for unfinished SaltPay backup processes after the card scanning operation. - * - * Returns [ScanChainException.PutSaltPayVisaCard] if backup is not finished. - * - * @param backupService a service responsible for managing backup operations, used here to check for unfinished backups - * and verify the primary card type. - * - * @see Chain for more information about the Chain interface. - */ -internal class CheckForUnfinishedSaltPayBackupChain( - private val backupService: BackupService, -) : Chain { - - override suspend fun invoke( - previousChainResult: ScanResponse, - ): Either { - if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) { - return previousChainResult.right() - } - - val isTheSamePrimaryCard = backupService.primaryCardId - ?.let { it == previousChainResult.card.cardId } - ?: false - - return if (previousChainResult.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) { - ScanChainException.PutSaltPayVisaCard.left() - } else { - previousChainResult.right() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt index 626465c7ee..6709cb8966 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/ScanChainException.kt @@ -5,11 +5,6 @@ import com.tangem.tap.common.redux.navigation.AppScreen sealed class ScanChainException : ScanCardException.ChainException() { - /** - * May be returned from [CheckForOnboardingChain] and [CheckForUnfinishedSaltPayBackupChain] - * */ - object PutSaltPayVisaCard : ScanChainException() - /** * May be returned from [DisclaimerChain] * */ @@ -21,11 +16,4 @@ sealed class ScanChainException : ScanCardException.ChainException() { * @param onboardingRoute route where to navigate * */ data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException() - - /** - * May be returned from [CheckForOnboardingChain] - * - * @param cause cause of exception - * */ - data class CheckForSaltPayOnboardingCaseException(override val cause: Throwable?) : ScanChainException() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt index f04df13017..53062e6918 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt @@ -4,7 +4,6 @@ import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError import com.tangem.domain.card.ScanCardException import com.tangem.tap.domain.scanCard.chains.ScanChainException -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.utils.converter.TwoWayConverter internal class ScanCardExceptionConverter : TwoWayConverter { @@ -35,10 +34,6 @@ internal class ScanCardExceptionConverter : TwoWayConverter TangemSdkError.UserCancelled() - is ScanChainException.PutSaltPayVisaCard -> TangemSdkError.ExceptionError( - SaltPayActivationError.PutVisaCard, - ) - is ScanChainException.CheckForSaltPayOnboardingCaseException, is ScanChainException.OnboardingNeeded, null, -> TangemSdkError.ExceptionError(e?.cause) diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 821e82be32..69a78dfb46 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -20,7 +20,6 @@ import com.tangem.crypto.CryptoUtils import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.common.TapWorkarounds.isExcluded import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation @@ -189,9 +188,7 @@ private class ScanWalletProcessor( val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId) @Suppress("ComplexCondition") - if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && - (activationInProgress || card.isSaltPay) - ) { + if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && activationInProgress) { StartPrimaryCardLinkingTask().run(session) { linkingResult -> when (linkingResult) { is CompletionResult.Success -> { @@ -213,10 +210,7 @@ private class ScanWalletProcessor( session: CardSession, callback: (result: CompletionResult) -> Unit, ) { - val productType = when (card.isSaltPay) { - true -> ProductType.SaltPay - else -> ProductType.Wallet - } + val productType = ProductType.Wallet scope.launch { val derivations = collectDerivations(card) if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) { diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index bb1c27c80e..ff7517ff1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -7,8 +7,8 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.datasource.files.AndroidFileReader import com.tangem.domain.models.scan.CardDTO -import com.tangem.tap.common.AndroidFileReader import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt index 8ebee84b97..b67bb497de 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensStorageService.kt @@ -4,7 +4,7 @@ import com.squareup.moshi.JsonAdapter import com.tangem.Log import com.tangem.datasource.api.common.MoshiConverter import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.tap.common.FileReader +import com.tangem.datasource.files.FileReader import com.tangem.tap.features.wallet.models.Currency class UserTokensStorageService(private val fileReader: FileReader) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt index 71415134a2..9d218c840f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt @@ -2,7 +2,6 @@ package com.tangem.tap.domain.userWalletList import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.attestation.OnlineCardVerifier @@ -43,7 +42,6 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV return when { cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL - SaltPayWorkaround.isSaltPayCardId(cardId) -> Artwork.SALT_PAY_URL else -> when (TwinsHelper.getTwinCardNumber(cardId)) { TwinCardNumber.First -> Artwork.TWIN_CARD_1 TwinCardNumber.Second -> Artwork.TWIN_CARD_2 diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 6b2aa5ccff..e276bd51db 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -2,9 +2,7 @@ package com.tangem.tap.domain.walletStores.repository.implementation import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionHistoryProvider import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.Result.Failure @@ -35,7 +33,6 @@ import com.tangem.tap.domain.walletStores.repository.implementation.utils.update import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent -import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithTxHistories import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage @@ -248,11 +245,6 @@ internal class DefaultWalletAmountsRepository( TestActions.testAmountInjectionForWalletManagerEnabled = false } else { walletManager.update() - - val wallet = walletManager.wallet - if (wallet.blockchain == Blockchain.SaltPay && walletManager is TransactionHistoryProvider) { - walletManager.getTransactionHistory(wallet.address, wallet.blockchain, wallet.getTokens()) - } } } @@ -349,7 +341,7 @@ internal class DefaultWalletAmountsRepository( it.updateWithDemoAmounts(wallet = updatedWallet) } else { it.updateWithAmounts(wallet = updatedWallet) - }.updateWithTxHistories(wallet = updatedWallet) + } }, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt index e342ff4e70..85fd9d5c8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletDataOperations.kt @@ -5,8 +5,6 @@ import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType import com.tangem.common.core.TangemError -import com.tangem.tap.common.extensions.getBlockchainTxHistory -import com.tangem.tap.common.extensions.getTokenTxHistory import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.model.WalletDataModel @@ -28,21 +26,6 @@ internal fun List.updateWithFiatRates(fiatRates: Map wallet.getBlockchainTxHistory() - is Currency.Token -> wallet.getTokenTxHistory(currency.token) - }, - ) -} - -internal fun List.updateWithTxHistories(wallet: Wallet): List { - return this.map { walletData -> - walletData.updateWithTxHistory(wallet) - } -} - internal fun List.updateWithAmounts(wallet: Wallet): List { return this.map { walletData -> walletData.updateWithAmount(wallet) @@ -170,7 +153,10 @@ internal fun List.updateWithMissedDerivation(): List.updateWithUnreachable(): List { return this.map { walletData -> walletData.copy( - status = WalletDataModel.Unreachable(errorMessage = null), + status = WalletDataModel.Unreachable( + errorMessage = null, + amount = walletData.status.amount, + ), ) } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 627c5f93d3..069f77fe4b 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -40,12 +40,6 @@ internal fun WalletStoreModel.updateWithError(wallet: Wallet, error: TangemError ) } -internal fun WalletStoreModel.updateWithTxHistories(wallet: Wallet): WalletStoreModel { - return this.copy( - walletsData = walletsData.updateWithTxHistories(wallet = wallet), - ) -} - internal fun WalletStoreModel.updateWithAmounts(wallet: Wallet): WalletStoreModel { return this.copy( walletsData = walletsData.updateWithAmounts(wallet = wallet), diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt index 5dec1d0850..9e08d07d76 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -5,11 +5,13 @@ import com.google.gson.GsonBuilder import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.calculateSha256 import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.tradeOrderSerializer import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData import com.tangem.tap.features.details.redux.walletconnect.TradeData import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder -import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer import timber.log.Timber object BnbHelper { @@ -39,6 +41,50 @@ object BnbHelper { ) } + fun WcBinanceTradeOrder.toWCBinanceTradeOrder(): WCBinanceTradeOrder { + return WCBinanceTradeOrder( + account_number = accountNumber, + chain_id = chainId, + data = data, + memo = memo, + sequence = sequence, + source = source, + msgs = msgs.map { it.toWCBinanceTradeOrderMessage() }, + ) + } + + private fun WcBinanceTradeOrder.Message.toWCBinanceTradeOrderMessage(): WCBinanceTradeOrder.Message { + return WCBinanceTradeOrder.Message(id, orderType, price, quantity, sender, side, symbol, timeInforce) + } + + fun WcBinanceTransferOrder.toWCBinanceTransferOrder(): WCBinanceTransferOrder { + return WCBinanceTransferOrder( + account_number = accountNumber, + chain_id = chainId, + data = data, + memo = memo, + sequence = sequence, + source = source, + msgs = msgs.map { it.toWCBinanceTransferOrderMessage() }, + ) + } + + private fun WcBinanceTransferOrder.Message.toWCBinanceTransferOrderMessage(): WCBinanceTransferOrder.Message { + return WCBinanceTransferOrder.Message( + inputs.map { it.toWCBinanceItem() }, + outputs.map { it.toWCBinanceItem() }, + ) + } + + private fun WcBinanceTransferOrder.Message.Item.toWCBinanceItem(): WCBinanceTransferOrder.Message.Item { + return WCBinanceTransferOrder.Message.Item( + address, + coins.map { + WCBinanceTransferOrder.Message.Item.Coin(it.amount, it.denom) + }, + ) + } + fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { val address = order.msgs.first().sender diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 64037f50e2..7b80ce1039 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -4,19 +4,18 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.walletconnect.extensions.isDappSupported -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.features.details.redux.walletconnect.WalletForSession -import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData -import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData -import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType +import com.tangem.tap.domain.walletconnect.extensions.toWcEthereumSignMessage +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData +import com.tangem.tap.features.details.redux.walletconnect.* import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.walletConnectRepository @@ -61,7 +60,7 @@ class WalletConnectManager { HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } } - private var sessions: MutableMap = mutableMapOf() + private var sessions: MutableMap = mutableMapOf() fun connect(wcUri: String) { val session = WCSession.from(wcUri).guard { @@ -73,7 +72,7 @@ class WalletConnectManager { ) return } - if (sessions[session] != null) { + if (sessions[session.topic] != null) { store.dispatchOnMain(WalletConnectAction.RefuseOpeningSession) return } @@ -93,7 +92,7 @@ class WalletConnectManager { return } - sessions[session] = WalletConnectActiveData( + sessions[session.topic] = WalletConnectActiveData( peerId = peerId, remotePeerId = null, session = session, @@ -104,26 +103,26 @@ class WalletConnectManager { } fun updateSession(session: WalletConnectSession) { - val updatedSession = sessions[session.session]?.copy( + val updatedSession = sessions[session.session.topic]?.copy( wallet = session.wallet, ) if (updatedSession != null) { - sessions[session.session] = updatedSession + sessions[session.session.topic] = updatedSession } } fun updateBlockchain(session: WalletConnectSession) { - sessions[session.session]?.client?.updateSession( + sessions[session.session.topic]?.client?.updateSession( accounts = listOfNotNull(session.getAddress()), chainId = session.wallet.blockchain?.getChainId(), approved = true, ) - val updatedSession = sessions[session.session]?.copy( + val updatedSession = sessions[session.session.topic]?.copy( wallet = session.wallet, ) if (updatedSession != null) { - sessions[session.session] = updatedSession + sessions[session.session.topic] = updatedSession } } @@ -131,7 +130,7 @@ class WalletConnectManager { private fun setupConnectionTimeoutCheck(session: WCSession) { scope.launch { delay(20_000) - val data = sessions[session] + val data = sessions[session.topic] if (data != null && data.peerMeta == null) { disconnect(session) store.dispatchOnMain(WalletConnectAction.OpeningSessionTimeout(session)) @@ -160,13 +159,13 @@ class WalletConnectManager { setListeners(it.client) it.client.connect(it.session, tangemPeerMeta, it.peerId, it.remotePeerId) } - }.associateBy { it.session }.toMutableMap() + }.associateBy { it.session.topic }.toMutableMap() store.dispatchOnMain(WalletConnectAction.SetSessionsRestored(sessions)) } fun approve(session: WCSession) { - val activeData = sessions[session] ?: return + val activeData = sessions[session.topic] ?: return removeSimilarSessions(activeData) val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return @@ -203,18 +202,18 @@ class WalletConnectManager { sessionsToRemove.forEach { disconnect(it.value.session) } } - fun rejectRequest(session: WCSession, id: Long) { - val activeData = sessions[session] ?: return + fun rejectRequest(topic: String, id: Long) { + val activeData = sessions[topic] ?: return activeData.client.rejectRequest(id) } - fun acceptRequest(session: WCSession, id: Long, data: String) { - val activeData = sessions[session] ?: return + fun acceptRequest(topic: String, id: Long, data: String) { + val activeData = sessions[topic] ?: return activeData.client.approveRequest(id, data) } fun disconnect(session: WCSession) { - val activeData = sessions[session] ?: return + val activeData = sessions[session.topic] ?: return val disconnected = if (activeData.client.isConnected) { activeData.client.killSession() } else { @@ -228,118 +227,145 @@ class WalletConnectManager { private fun onSessionClosed(session: WCSession) { Analytics.send(WalletConnect.SessionDisconnected()) - sessions.remove(session) + sessions.remove(session.topic) walletConnectRepository.removeSession(session) store.dispatchOnMain(WalletConnectAction.RemoveSession(session)) } fun handleTransactionRequest( - transaction: WCEthereumTransaction, + transaction: WcEthereumTransaction, session: WalletConnectSession, id: Long, - type: WcTransactionType, + type: WcEthTransactionType, ) { - val activeData = sessions[session.session] ?: return + val activeData = sessions[session.session.topic] ?: return scope.launch { val data = WalletConnectSdkHelper().prepareTransactionData( - transaction = transaction, - session = session, - id = id, - type = type, + EthTransactionData( + transaction = transaction, + networkId = session.wallet.blockchain?.toNetworkId() ?: "", + rawDerivationPath = session.wallet.derivationPath?.rawPath, + id = id, + topic = session.session.topic, + type = type, + metaName = session.peerMeta.name, + metaUrl = session.peerMeta.url, + ), ).guard { - sessions[session.session] = activeData.copy(transactionData = null) + sessions[session.session.topic] = activeData.copy(transactionData = null) store.dispatchOnMain( WalletConnectAction.RejectRequest( - session.session, + session.session.topic, id, ), ) return@launch } - sessions[session.session] = activeData.copy(transactionData = data) + sessions[session.session.topic] = activeData.copy(transactionData = data) store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.RequestTransaction( - data.dialogData, + WcPreparedRequest.EthTransaction( + preparedRequestData = data, + topic = session.session.topic, + requestId = id, + derivationPath = data.walletManager.wallet.publicKey.derivationPath?.rawPath, + ), ), ), ) } } - fun completeTransaction(session: WCSession) { - val activeData = sessions[session] + fun completeTransaction(topic: Topic) { + val activeData = sessions[topic] val data = activeData?.transactionData ?: return scope.launch { val hash = WalletConnectSdkHelper().completeTransaction(data, cardId).guard { - sessions[data.session.session] = activeData.copy(transactionData = null) + sessions[topic] = activeData.copy(transactionData = null) store.dispatchOnMain( WalletConnectAction.RejectRequest( - data.session.session, + topic, data.id, ), ) return@launch } - acceptRequest(session, data.id, hash) - sessions[session] = activeData.copy(transactionData = null) + acceptRequest(topic, data.id, hash) + sessions[topic] = activeData.copy(transactionData = null) } } - fun signBnb(id: Long, data: ByteArray, sessionData: WCSession) { - val activeData = sessions[sessionData] ?: return + fun signBnb(id: Long, data: ByteArray, topic: Topic) { + val activeData = sessions[topic] ?: return scope.launch { - val hash = WalletConnectSdkHelper().signBnbTransaction(data, activeData, cardId).guard { + val hash = WalletConnectSdkHelper().signBnbTransaction( + data = data, + networkId = activeData.wallet.blockchain?.toNetworkId() ?: "", + derivationPath = activeData.wallet.derivationPath?.rawPath, + cardId = cardId, + ).guard { store.dispatchOnMain( WalletConnectAction.RejectRequest( - sessionData, + topic, id, ), ) return@launch } - acceptRequest(sessionData, id, hash) + acceptRequest(topic, id, hash) } } fun handlePersonalSignRequest(message: WCEthereumSignMessage, session: WalletConnectSession, id: Long) { - val activeData = sessions[session.session] ?: return + val activeData = sessions[session.session.topic] ?: return scope.launch { val data = WalletConnectSdkHelper().prepareDataForPersonalSign( - message = message, - session = session, + message = message.toWcEthereumSignMessage(), + topic = session.session.topic, id = id, + metaName = session.peerMeta.name, ) - sessions[session.session] = activeData.copy(personalSignData = data) + sessions[session.session.topic] = activeData.copy(personalSignData = data) store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.PersonalSign( - data.dialogData, + WcPreparedRequest.EthSign( + preparedRequestData = data, + topic = session.session.topic, + requestId = id, + derivationPath = session.wallet.derivationPath?.rawPath, + ), ), ), ) } } - fun sendSignedMessage(session: WCSession) { - val activeData = sessions[session] + fun sendSignedMessage(topic: Topic) { + val activeData = sessions[topic] val data = activeData?.personalSignData ?: return scope.launch { - val hash = WalletConnectSdkHelper().signPersonalMessage(data.hash, activeData.wallet, cardId) + val hash = WalletConnectSdkHelper().signPersonalMessage( + hashToSign = data.hash, + networkId = activeData.wallet.blockchain?.toNetworkId() ?: "", + derivationPath = activeData.wallet.derivationPath?.rawPath, + cardId = cardId, + ) .guard { - sessions[data.session.session] = activeData.copy(transactionData = null) + sessions[topic] = activeData.copy(transactionData = null) store.dispatchOnMain( WalletConnectAction.RejectRequest( - data.session.session, + topic, data.id, ), ) return@launch } - sessions[session] = activeData.copy(personalSignData = data) - acceptRequest(data.session.session, data.id, hash) - sessions[data.session.session] = activeData.copy(transactionData = null) + sessions[topic] = activeData.copy(personalSignData = data) + acceptRequest(topic, data.id, hash) + sessions[topic] = activeData.copy(transactionData = null) } } @@ -348,7 +374,7 @@ class WalletConnectManager { client.onSessionRequest = { id: Long, peer: WCPeerMeta -> Timber.d("OnSessionRequest: $peer") val session = client.session - val data = sessions[session]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId) + val data = sessions[session?.topic]?.copy(peerMeta = peer, remotePeerId = client.remotePeerId) if (data != null && session != null) { if (!peer.isDappSupported()) { store.dispatchOnMain( @@ -358,7 +384,7 @@ class WalletConnectManager { ), ) } else { - sessions[session] = data + sessions[session.topic] = data val sessionData = data.toWalletConnectSession() sessionData?.let { store.dispatchOnMain( @@ -384,13 +410,13 @@ class WalletConnectManager { // AnalyticsAnOld.WcAction.SendTransaction // ) // ) - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandleTransactionRequest( transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSendTransaction, + type = WcEthTransactionType.EthSendTransaction, ), ) } @@ -402,13 +428,13 @@ class WalletConnectManager { // AnalyticsAnOld.WcAction.SignTransaction // ) // ) - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandleTransactionRequest( transaction = transaction, session = sessionData, id = id, - type = WcTransactionType.EthSignTransaction, + type = WcEthTransactionType.EthSignTransaction, ), ) } @@ -420,7 +446,7 @@ class WalletConnectManager { // AnalyticsAnOld.WcAction.PersonalSign // ) // ) - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.HandlePersonalSignRequest( message, @@ -434,7 +460,7 @@ class WalletConnectManager { client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder -> } client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder -> - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Trade( id = id, @@ -445,7 +471,7 @@ class WalletConnectManager { } } client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder -> - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + sessions[client.session?.topic]?.toWalletConnectSession()?.let { sessionData -> store.dispatchOnMain( WalletConnectAction.BinanceTransaction.Transfer( id = id, @@ -484,7 +510,7 @@ class WalletConnectManager { private fun switchChain(chainId: Int, client: WCClient) { val blockchain = Blockchain.fromChainId(chainId) Timber.d("WC switch chainID\nNew Blockchain: $blockchain") - val session = sessions[client.session]?.toWalletConnectSession() + val session = sessions[client.session?.topic]?.toWalletConnectSession() if (session != null) { store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) } @@ -499,6 +525,8 @@ class WalletConnectManager { } } +typealias Topic = String + data class WalletConnectActiveData( val peerId: String, val remotePeerId: String?, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index af3a5c19f9..80c07fe5f7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -5,14 +5,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.CommonSigner -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionSender -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -24,41 +17,40 @@ import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString import com.tangem.core.analytics.Analytics -import com.tangem.crypto.CryptoUtils +import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType import com.tangem.tap.common.analytics.events.WalletConnect import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString -import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession -import com.tangem.tap.features.details.redux.walletconnect.WalletForSession -import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData -import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData -import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder +import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction +import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder +import com.tangem.tap.features.details.redux.walletconnect.* +import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.tangem.tap.store import com.tangem.tap.tangemSdk import com.tangem.tap.tangemSdkManager -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.MESSAGE -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.* import timber.log.Timber import java.math.BigDecimal class WalletConnectSdkHelper { @Suppress("MagicNumber") - suspend fun prepareTransactionData( - transaction: WCEthereumTransaction, - session: WalletConnectSession, - id: Long, - type: WcTransactionType, - ): WcTransactionData? { - val walletManager = getWalletManager(session) ?: return null + suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? { + val transaction = data.transaction + val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null + val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null walletManager.safeUpdate() val wallet = walletManager.wallet @@ -95,53 +87,49 @@ class WalletConnectSdkHelper { nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(), ), ) + val dialogData = TransactionRequestDialogData( - dAppName = session.peerMeta.name, - dAppUrl = session.peerMeta.url, + dAppName = data.metaName, + dAppUrl = data.metaUrl, amount = value.toFormattedString(decimals), gasAmount = fee.toFormattedString(decimals), totalAmount = total.toFormattedString(decimals), balance = balance.toFormattedString(decimals), isEnoughFundsToSend = balance - total >= BigDecimal.ZERO, - session = session.session, - id = id, - type = type, + topic = data.topic, + id = data.id, + type = data.type, ) return WcTransactionData( - type = type, + type = data.type, transaction = transactionData, - session = session, - id = id, + topic = data.topic, + id = data.id, walletManager = walletManager, dialogData = dialogData, ) } - private fun getWalletManager(session: WalletConnectSession): WalletManager? { - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val publicKey = Wallet.PublicKey( - session.wallet.walletPublicKey ?: return null, - session.wallet.derivedPublicKey, - session.wallet.derivationPath, - ) - val blockchain = session.wallet.getBlockchainForSession() - return factory.makeWalletManager( + private fun getWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager? { + val blockchainNetwork = BlockchainNetwork( blockchain = blockchain, - publicKey = publicKey, + derivationPath = derivationPath, + tokens = emptyList(), ) + return store.state.walletState.getWalletManager(blockchainNetwork) } suspend fun completeTransaction(data: WcTransactionData, cardId: String?): String? { return when (data.type) { - WcTransactionType.EthSendTransaction -> sendTransaction(data, cardId) - WcTransactionType.EthSignTransaction -> signTransaction(data, cardId) + WcEthTransactionType.EthSendTransaction -> sendTransaction(data, cardId) + WcEthTransactionType.EthSignTransaction -> signTransaction(data, cardId) } } private suspend fun getGasLimitFromTx( value: BigDecimal, walletManager: WalletManager, - transaction: WCEthereumTransaction, + transaction: WcEthereumTransaction, ): BigDecimal { return transaction.gas?.hexToBigDecimal() ?: transaction.gasLimit?.hexToBigDecimal() @@ -155,7 +143,7 @@ class WalletConnectSdkHelper { private suspend fun getGaLimitFromBlockchain( value: BigDecimal, walletManager: WalletManager, - transaction: WCEthereumTransaction, + transaction: WcEthereumTransaction, ): BigDecimal { val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit( amount = Amount(value, walletManager.wallet.blockchain), @@ -180,7 +168,7 @@ class WalletConnectSdkHelper { return when (result) { SimpleResult.Success -> { val sentFrom = AnalyticsParam.TxSentFrom.WalletConnect - Analytics.send(Basic.TransactionSent(sentFrom)) + Analytics.send(Basic.TransactionSent(sentFrom = sentFrom, memoType = MemoType.Null)) HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash } is SimpleResult.Failure -> { @@ -216,17 +204,32 @@ class WalletConnectSdkHelper { } } - suspend fun signBnbTransaction(data: ByteArray, session: WalletConnectActiveData, cardId: String?): String? { + fun prepareBnbTradeOrder(data: WcBinanceTradeOrder): BinanceMessageData.Trade { + return BnbHelper.createMessageData(data.toWCBinanceTradeOrder()) + } + + fun prepareBnbTransferOrder(data: WcBinanceTransferOrder): BinanceMessageData.Transfer { + return BnbHelper.createMessageData(data.toWCBinanceTransferOrder()) + } + + suspend fun signBnbTransaction( + data: ByteArray, + networkId: String, + derivationPath: String?, + cardId: String?, + ): String? { + val blockchain = Blockchain.fromNetworkId(networkId) ?: return null + val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null + val command = SignHashCommand( hash = data, - walletPublicKey = session.wallet.walletPublicKey ?: return null, - derivationPath = session.wallet.derivationPath, + walletPublicKey = wallet.publicKey.seedKey, + derivationPath = wallet.publicKey.derivationPath, ) val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId) return when (result) { is CompletionResult.Success -> { - val key = session.wallet.derivedPublicKey?.toDecompressedPublicKey() - ?: session.wallet.walletPublicKey.toDecompressedPublicKey() + val key = wallet.publicKey.blockchainKey.toDecompressedPublicKey() getBnbResultString( key.toHexString(), result.data.signature.toHexString(), @@ -241,13 +244,16 @@ class WalletConnectSdkHelper { } fun prepareDataForPersonalSign( - message: WCEthereumSignMessage, - session: WalletConnectSession, + message: WcEthereumSignMessage, + topic: String, + metaName: String, id: Long, ): WcPersonalSignData { val messageData = when (message.type) { - MESSAGE, PERSONAL_MESSAGE -> createMessageData(message) - TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data) + WcEthereumSignMessage.WCSignType.MESSAGE, + WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE, + -> createMessageData(message) + WcEthereumSignMessage.WCSignType.TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data) } val messageString = message.data.hexToAscii() @@ -255,20 +261,21 @@ class WalletConnectSdkHelper { ?: message.data val dialogData = PersonalSignDialogData( - dAppName = session.peerMeta.name, + dAppName = metaName, message = messageString, - session = session.session, + topic = topic, id = id, ) return WcPersonalSignData( hash = messageData, - session = session, + topic = topic, id = id, dialogData = dialogData, + type = message.type, ) } - private fun createMessageData(message: WCEthereumSignMessage): ByteArray { + private fun createMessageData(message: WcEthereumSignMessage): ByteArray { val messageData = try { message.data.removePrefix(HEX_PREFIX).hexToBytes() } catch (exception: Exception) { @@ -299,16 +306,27 @@ class WalletConnectSdkHelper { }.joinToString("") } - suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession, cardId: String?): String? { - val key = wallet.derivedPublicKey ?: wallet.walletPublicKey - val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath) + suspend fun signPersonalMessage( + hashToSign: ByteArray, + networkId: String, + derivationPath: String?, + cardId: String?, + ): String? { + val blockchain = Blockchain.fromNetworkId(networkId) ?: return null + val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null + + val command = SignHashCommand( + hash = hashToSign, + walletPublicKey = wallet.publicKey.seedKey, + derivationPath = wallet.publicKey.derivationPath, + ) return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) { is CompletionResult.Success -> { val hash = result.data.signature return EthereumUtils.prepareSignedMessageData( signedHash = hash, hashToSign = hashToSign, - publicKey = CryptoUtils.decompressPublicKey(key!!), + publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(), ) } is CompletionResult.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt index 057caf7bd2..3197f2729c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt @@ -1,9 +1,39 @@ package com.tangem.tap.domain.walletconnect.extensions +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction import com.trustwallet.walletconnect.models.WCPeerMeta +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage +import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction fun WCPeerMeta.isDappSupported(): Boolean { return !unsupportedDappsList.any { this.url.contains(it) } } -private val unsupportedDappsList: List = listOf("dydx.exchange") \ No newline at end of file +private val unsupportedDappsList: List = listOf("dydx.exchange") + +fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction { + return WcEthereumTransaction( + from = from, + to = to, + nonce = nonce, + gasPrice = gasPrice, + maxFeePerGas = maxFeePerGas, + maxPriorityFeePerGas = maxPriorityFeePerGas, + gas = gas, + gasLimit = gasLimit, + value = value, + data = data, + ) +} + +fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcEthereumSignMessage { + return WcEthereumSignMessage( + raw = raw, + type = when (type) { + WCEthereumSignMessage.WCSignType.MESSAGE -> WcEthereumSignMessage.WCSignType.MESSAGE + WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE + WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcEthereumSignMessage.WCSignType.TYPED_MESSAGE + }, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt new file mode 100644 index 0000000000..6a584a0548 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -0,0 +1,63 @@ +package com.tangem.tap.domain.walletconnect2.app + +import com.tangem.blockchain.common.* +import com.tangem.domain.common.extensions.* +import com.tangem.tap.domain.walletconnect2.domain.* + +class TangemWcBlockchainHelper : WcBlockchainHelper { + override fun chainIdToNetworkIdOrNull(chainId: String): String? { + val parsed = chainId.split(CHAIN_SEPARATOR) + if (parsed.size != 2) return null + when { + parsed.first() == EVM_NAMESPACE -> { + val chainIdInt = parsed[1].toIntOrNull() ?: return null + return Blockchain.fromChainId(chainIdInt)?.toNetworkId() + } + else -> { + val blockchain = Blockchain.fromNetworkId(parsed.first()) + return if (supportedNonEvmBlockchains.contains(blockchain)) blockchain?.toNetworkId() else null + } + } + } + + override fun networkIdToChainIdOrNull(networkId: String): String? { + val blockchain = Blockchain.fromNetworkId(networkId) + val namespace = blockchain?.getCaip2Namespace() ?: return null + val chainId = blockchain.getCaip2ChainId() ?: return null + return "$namespace$CHAIN_SEPARATOR$chainId" + } + + override fun getNamespaceFromFullChainIdOrNull(chainId: String): String? { + val parsed = chainId.split(CHAIN_SEPARATOR) + return parsed.firstOrNull() + } + + override fun chainIdToFullNameOrNull(chainId: String): String? { + val networkId = chainIdToNetworkIdOrNull(chainId) ?: return null + return Blockchain.fromNetworkId(networkId)?.fullName + } +} + +private fun Blockchain.getCaip2ChainId(): String? { + if (this.isEvm()) return this.getChainId()?.toString() + + return when (this) { + Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ" + Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182" + Blockchain.Tron -> "0x2b6653dc" + else -> null + } +} + +private fun Blockchain.getCaip2Namespace(): String? { + return when { + this.isEvm() -> EVM_NAMESPACE + supportedNonEvmBlockchains.contains(this) -> this.toNetworkId() + else -> null + } +} + +private val supportedNonEvmBlockchains = emptySet() // TODO: add supported networks + +private const val EVM_NAMESPACE = "eip155" +private const val CHAIN_SEPARATOR = ":" \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt new file mode 100644 index 0000000000..d6767ddc8e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt @@ -0,0 +1,49 @@ +package com.tangem.tap.domain.walletconnect2.app + +import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectEventsHandler +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog +import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen +import com.tangem.tap.store +import timber.log.Timber + +class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler { + override fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String) { + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.SessionProposalDialog( + sessionProposal = proposal, + networks = networksFormatted, + onApprove = { store.dispatchOnMain(WalletConnectAction.ApproveProposal) }, + onReject = { store.dispatchOnMain(WalletConnectAction.RejectProposal) }, + ), + ), + ) + } + + override fun onSessionEstablished() { + store.dispatchOnMain(WalletConnectAction.SessionEstablished) + } + + override fun onSessionRejected(error: WalletConnectError) { + store.dispatchOnMain(WalletConnectAction.SessionRejected(error)) + } + + override fun onListOfSessionsUpdated(sessions: List) { + Timber.d("WC2: List of sessions updated. Sessions: $sessions") + store.dispatchOnMain(WalletConnectAction.SessionListUpdated(sessions)) + } + + override fun onSessionRequest(request: WcPreparedRequest) { + store.dispatchOnMain(WalletConnectAction.ShowSessionRequest(request)) + } + + override fun onUnsupportedRequest() { + store.dispatchOnMain(WalletConnectAction.RejectUnsupportedRequest) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt new file mode 100644 index 0000000000..80d7ed0b09 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt @@ -0,0 +1,327 @@ +package com.tangem.tap.domain.walletconnect2.data + +import android.app.* +import com.tangem.tap.domain.walletconnect2.domain.* +import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.walletconnect.android.* +import com.walletconnect.android.relay.* +import com.walletconnect.web3.wallet.client.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import timber.log.* +import javax.inject.* + +class WalletConnectRepositoryImpl @Inject constructor( + private val application: Application, + private val wcRequestDeserializer: WcJrpcRequestsDeserializer, +) : WalletConnectRepository { + + private var sessionProposal: Wallet.Model.SessionProposal? = null + private var userNamespaces: Map>? = null + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + private val _events: MutableSharedFlow = MutableSharedFlow() + override val events: Flow = _events + + private val _activeSessions: MutableSharedFlow> = MutableSharedFlow() + override val activeSessions: Flow> = _activeSessions + + /** + * @param projectId Project ID at https://cloud.walletconnect.com/ + */ + override fun init(projectId: String) { + val relayUrl = "relay.walletconnect.com" + val serverUrl = "wss://$relayUrl?projectId=$projectId" + val connectionType = ConnectionType.AUTOMATIC + + val appMetaData = Core.Model.AppMetaData( + name = "Tangem", + description = "Tangem Wallet", + url = "tangem.com", + icons = listOf( + "https://user-images.githubusercontent.com/24321494/124071202-72a00900-da58-11eb-935a-dcdab21de52b.png", + ), + redirect = "kotlin-wallet-wc:/request", // Custom Redirect URI + ) + + CoreClient.initialize( + relayServerUrl = serverUrl, + connectionType = connectionType, + application = application, + metaData = appMetaData, + ) { error -> + Timber.d("Error while initializing client: $error") + scope.launch { + _events.emit( + WalletConnectEvents.SessionApprovalError( + WalletConnectError.ExternalApprovalError(error.throwable.message), + ), + ) + } + } + + Web3Wallet.initialize(Wallet.Params.Init(core = CoreClient)) { error -> + Timber.d("Error while initializing Web3Wallet: $error") + scope.launch { + _events.emit( + WalletConnectEvents.SessionApprovalError( + WalletConnectError.ExternalApprovalError(error.throwable.message), + ), + ) + } + } + + val walletDelegate = defineWalletDelegate() + Web3Wallet.setWalletDelegate(walletDelegate) + } + + private fun defineWalletDelegate(): Web3Wallet.WalletDelegate { + return object : Web3Wallet.WalletDelegate { + override fun onSessionProposal(sessionProposal: Wallet.Model.SessionProposal) { + // Triggered when wallet receives the session proposal sent by a Dapp + Timber.d("sessionProposal: $sessionProposal") + this@WalletConnectRepositoryImpl.sessionProposal = sessionProposal + + scope.launch { + _events.emit( + WalletConnectEvents.SessionProposal( + sessionProposal.name, + sessionProposal.description, + sessionProposal.url, + sessionProposal.icons, + sessionProposal.requiredNamespaces.values.flatMap { it.chains ?: emptyList() }, + ), + ) + } + } + + override fun onSessionRequest(sessionRequest: Wallet.Model.SessionRequest) { + // Triggered when a Dapp sends SessionRequest to sign a transaction or a message + Timber.d("sessionRequest: $sessionRequest") + val request = wcRequestDeserializer.deserialize( + method = sessionRequest.request.method, + params = sessionRequest.request.params, + ) + Timber.d("sessionRequestParsed: $request") + + scope.launch { + _events.emit( + WalletConnectEvents.SessionRequest( + request = request, + chainId = sessionRequest.chainId, + topic = sessionRequest.topic, + id = sessionRequest.request.id, + metaUrl = sessionRequest.peerMetaData?.url ?: "", + metaName = sessionRequest.peerMetaData?.name ?: "", + ), + ) + } + } + + override fun onAuthRequest(authRequest: Wallet.Model.AuthRequest) { + // Triggered when Dapp / Requester makes an authorization request + } + + override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) { + // Triggered when the session is deleted by the peer + if (sessionDelete is Wallet.Model.SessionDelete.Success) { + scope.launch { + _events.emit(WalletConnectEvents.SessionDeleted(sessionDelete.topic)) + } + updateSessions() + } + Timber.d("onSessionDelete: $sessionDelete") + } + + override fun onSessionSettleResponse(settleSessionResponse: Wallet.Model.SettledSessionResponse) { + // Triggered when wallet receives the session settlement response from Dapp + Timber.d("onSessionSettleResponse: $settleSessionResponse") + if (settleSessionResponse is Wallet.Model.SettledSessionResponse.Result) { + scope.launch { + _events.emit( + WalletConnectEvents.SessionApprovalSuccess( + topic = settleSessionResponse.session.topic, + accounts = userNamespaces?.flatMap { it.value } ?: emptyList(), + ), + ) + } + updateSessions() + } + } + + override fun onSessionUpdateResponse(sessionUpdateResponse: Wallet.Model.SessionUpdateResponse) { + // Triggered when wallet receives the session update response from Dapp + Timber.d("onSessionUpdateResponse: $sessionUpdateResponse") + updateSessions() + } + + override fun onConnectionStateChange(state: Wallet.Model.ConnectionState) { + // Triggered whenever the connection state is changed + Timber.d("onConnectionStateChange: $state") + if (state.isAvailable) updateSessions() + } + + override fun onError(error: Wallet.Model.Error) { + // Triggered whenever there is an issue inside the SDK + } + } + } + + override fun pair(uri: String) { + Web3Wallet.pair(Wallet.Params.Pair(uri)) + } + + override fun approve(userNamespaces: Map>) { + this.userNamespaces = userNamespaces + + val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal) + + val missingNetworks = findMissingNetworks( + namespaces = sessionProposal.requiredNamespaces, + userNamespaces = userNamespaces, + ) + if (missingNetworks.isNotEmpty()) { + Timber.e("Unsupported blockchains: $missingNetworks") + scope.launch { + _events.emit( + WalletConnectEvents.SessionApprovalError( + WalletConnectError.ApprovalErrorMissingNetworks(missingNetworks.toList()), + ), + ) + } + return + } + + val userChains = userNamespaces.flatMap { namespace -> + namespace.value.map { it.chainId to "${it.chainId}:${it.walletAddress}" } + }.groupBy { pair -> pair.first } + .mapValues { entry -> entry.value.map { pair -> pair.second }.toSet() } + + val preparedNamespaces = sessionProposal.requiredNamespaces.map { requiredNamespace -> + val accounts = requiredNamespace.value.chains?.mapNotNull { userChains[it] }?.flatten() ?: emptyList() + + requiredNamespace.key to Wallet.Model.Namespace.Session( + accounts = accounts, + methods = requiredNamespace.value.methods, + events = requiredNamespace.value.events, + ) + }.toMap() + + val sessionApproval = Wallet.Params.SessionApprove(sessionProposal.proposerPublicKey, preparedNamespaces) + + Timber.d("Session approval is prepared for sending: $sessionApproval") + + Web3Wallet.approveSession( + params = sessionApproval, + onSuccess = { + Timber.d("Approved successfully: $it") + }, + onError = { + Timber.d("Error while approving: $it") + scope.launch { + _events.emit( + WalletConnectEvents.SessionApprovalError( + WalletConnectError.ExternalApprovalError(it.throwable.message), + ), + ) + } + }, + ) + } + + override fun sendRequest(topic: String, id: Long, result: String) { + Web3Wallet.respondSessionRequest( + params = Wallet.Params.SessionRequestResponse( + sessionTopic = topic, + jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult( + id = id, + result = result, + ), + ), + onSuccess = {}, + onError = {}, + ) + } + + override fun rejectRequest(topic: String, id: Long) { + Web3Wallet.respondSessionRequest( + params = Wallet.Params.SessionRequestResponse( + sessionTopic = topic, + jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError( + id = id, + code = 0, + message = "", + ), + ), + onSuccess = {}, + onError = {}, + ) + } + + override fun reject() { + Web3Wallet.rejectSession( + params = Wallet.Params.SessionReject( + sessionProposal?.proposerPublicKey ?: "", + "", + ), + onSuccess = { + Timber.d("Rejected successfully: $it") + }, + onError = { + Timber.d("Error while rejecting: $it") + }, + ) + } + + override fun disconnect(topic: String) { + Web3Wallet.disconnectSession( + params = Wallet.Params.SessionDisconnect(topic), + onSuccess = { + updateSessions() + Timber.d("Disconnected successfully: $it") + }, + onError = { + Timber.d("Error while disconnecting: $it") + }, + ) + } + + fun send(topic: String, id: Long, data: String) { + Web3Wallet.respondSessionRequest( + params = Wallet.Params.SessionRequestResponse( + sessionTopic = topic, + jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcResult( + id = id, + result = data, + ), + ), + onError = {}, + onSuccess = {}, + ) + } + + override fun updateSessions() { + scope.launch { + val availableSessions = Web3Wallet.getListOfActiveSessions() + .map { + WalletConnectSession( + topic = it.topic, + icon = it.metaData?.icons?.firstOrNull(), + name = it.metaData?.name, + url = it.metaData?.url, + ) + } + Timber.d("Available sessions: $availableSessions") + _activeSessions.emit(availableSessions) + } + } + + private fun findMissingNetworks( + namespaces: Map, + userNamespaces: Map>, + ): Collection { + val requiredChains = namespaces.values.flatMap { it.chains ?: emptyList() } + val userChains = userNamespaces.flatMap { it.value.map { account -> account.chainId } } + return requiredChains.subtract(userChains.toSet()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt new file mode 100644 index 0000000000..fe3aab634c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt @@ -0,0 +1,59 @@ +package com.tangem.tap.domain.walletconnect2.data + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.tangem.datasource.di.SdkMoshi +import com.tangem.datasource.files.FileReader +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.tap.domain.walletconnect2.domain.models.Session +import timber.log.Timber +import javax.inject.Inject + +class WalletConnectSessionsRepositoryImpl @Inject constructor( + @SdkMoshi private val moshi: Moshi, + private val fileReader: FileReader, +) : + WalletConnectSessionsRepository { + + private val sessionsAdapter: JsonAdapter> = moshi.adapter( + Types.newParameterizedType(List::class.java, Session::class.java), + ) + + override suspend fun loadSessions(userWallet: String): List { + return try { + val fileContent = fileReader.readFile(getFileNameForUserWallet(userWallet)) + sessionsAdapter.fromJson(fileContent) ?: emptyList() + } catch (exception: Exception) { + Timber.e(exception) + emptyList() + } + } + + override suspend fun saveSession(userWallet: String, session: Session) { + val updatedList = loadSessions(userWallet).plus(session) + writeSessionToFile(sessions = updatedList, userWallet = userWallet) + } + + override suspend fun removeSession(userWallet: String, topic: String) { + val updatedList = loadSessions(userWallet).filterNot { it.topic == topic } + writeSessionToFile(sessions = updatedList, userWallet = userWallet) + } + + private fun writeSessionToFile(sessions: List, userWallet: String) { + val serialized = sessionsAdapter.toJson(sessions) + try { + fileReader.rewriteFile(serialized, getFileNameForUserWallet(userWallet)) + } catch (exception: Exception) { + Timber.e(exception) + } + } + + companion object { + private const val FILE_NAME_PREFIX = "wc_2" + + private fun getFileNameForUserWallet(userWallet: String): String { + return "$FILE_NAME_PREFIX-${userWallet.uppercase()}" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt new file mode 100644 index 0000000000..8cf7905260 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -0,0 +1,54 @@ +package com.tangem.tap.domain.walletconnect2.di + +import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper +import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper +import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl +import com.tangem.tap.domain.walletconnect2.data.WalletConnectRepositoryImpl +import com.tangem.tap.domain.walletconnect2.data.WalletConnectSessionsRepositoryImpl +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.scopes.ActivityScoped +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(ActivityComponent::class) +class WalletConnectInteractorModule { + @Provides + @ActivityScoped + fun provideWalletConnectInteractor( + wcRepository: WalletConnectRepository, + wcSessionsRepository: WalletConnectSessionsRepository, + ): WalletConnectInteractor { + return WalletConnectInteractor( + handler = WalletConnectEventsHandlerImpl(), + walletConnectRepository = wcRepository, + sessionsRepository = wcSessionsRepository, + sdkHelper = WalletConnectSdkHelper(), + blockchainHelper = TangemWcBlockchainHelper(), + dispatcher = AppCoroutineDispatcherProvider(), + ) + } +} + +@Module +@InstallIn(SingletonComponent::class) +interface WalletConnectModule { + + @Binds + @Singleton + fun bindWalletConnectRepository(repository: WalletConnectRepositoryImpl): WalletConnectRepository + + @Binds + @Singleton + fun bindWalletConnectSessionsRepository( + repository: WalletConnectSessionsRepositoryImpl, + ): WalletConnectSessionsRepository +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectEventsHandler.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectEventsHandler.kt new file mode 100644 index 0000000000..30c73a0642 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectEventsHandler.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen + +interface WalletConnectEventsHandler { + fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String) + + fun onSessionEstablished() + + fun onSessionRejected(error: WalletConnectError) + + fun onListOfSessionsUpdated(sessions: List) + + fun onSessionRequest(request: WcPreparedRequest) + + fun onUnsupportedRequest() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt new file mode 100644 index 0000000000..03bd4e548e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -0,0 +1,224 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.common.extensions.* +import com.tangem.tap.domain.walletconnect.* +import com.tangem.tap.domain.walletconnect2.domain.models.* +import com.tangem.tap.features.details.ui.walletconnect.* +import com.tangem.utils.coroutines.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import timber.log.* + +class WalletConnectInteractor( + private val handler: WalletConnectEventsHandler, + private val walletConnectRepository: WalletConnectRepository, + private val sessionsRepository: WalletConnectSessionsRepository, + private val sdkHelper: WalletConnectSdkHelper, + private val dispatcher: CoroutineDispatcherProvider, + val blockchainHelper: WcBlockchainHelper, +) { + + private val events = walletConnectRepository.events + private val sessions = walletConnectRepository.activeSessions + + private var userWalletId: String = "" + private var cardId: String? = null + + private var currentRequest: WalletConnectEvents.SessionRequest? = null + private val sessionRequestConverter = WcSessionRequestConverter( + blockchainHelper = blockchainHelper, + sessionsRepository = sessionsRepository, + sdkHelper = sdkHelper, + ) + + suspend fun startListening(userWalletId: String, cardId: String?) { + this.userWalletId = userWalletId + this.cardId = cardId + coroutineScope { + launch { subscribeToEvents() } + launch { subscribeToSessions() } + } + walletConnectRepository.updateSessions() + } + + private suspend fun subscribeToEvents() { + events + .onEach { wcEvent -> + when (wcEvent) { + is WalletConnectEvents.SessionProposal -> { + Timber.d("WC session proposal event received") + val networksFormatted = wcEvent.chainIds + .mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) } + .toString() + handler.onProposalReceived(proposal = wcEvent, networksFormatted = networksFormatted) + } + is WalletConnectEvents.SessionApprovalError -> { + val error = when (wcEvent.error) { + is WalletConnectError.ApprovalErrorMissingNetworks -> { + val missingNetworks = wcEvent.error.missingChains + .map { blockchainHelper.chainIdToNetworkIdOrNull(it) } + val containsUnsupportedNetworks = missingNetworks.any { it == null } + if (containsUnsupportedNetworks) { + WalletConnectError.ApprovalErrorUnsupportedNetwork + } else { + WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull()) + } + } + else -> wcEvent.error + } + handler.onSessionRejected(error) + } + is WalletConnectEvents.SessionApprovalSuccess -> { + sessionsRepository.saveSession( + userWallet = userWalletId, + session = Session.fromAccounts( + accounts = wcEvent.accounts, + topic = wcEvent.topic, + ), + ) + handler.onSessionEstablished() + } + is WalletConnectEvents.SessionDeleted -> { + sessionsRepository.removeSession(userWalletId, wcEvent.topic) + } + is WalletConnectEvents.SessionRequest -> { + handleRequest(wcEvent) + } + } + } + .flowOn(dispatcher.io) + .collect() + } + + private suspend fun subscribeToSessions() { + sessions + .onEach { listOfSessions -> + val relevantTopics = getTopicsForUserWallet(userWalletId, sessionsRepository) + val filteredSessions = filterSessionsForUserWallet(listOfSessions, relevantTopics) + handler.onListOfSessionsUpdated(filteredSessions) + } + .flowOn(dispatcher.io) + .collect() + } + + private fun filterSessionsForUserWallet( + availableSessions: List, + relevantTopics: List, + ): List { + return availableSessions.filter { relevantTopics.contains(it.topic) } + .map { + WcSessionForScreen( + description = it.name ?: "", + sessionId = it.topic, + ) + } + } + + private suspend fun getTopicsForUserWallet( + userWalletId: String, + repository: WalletConnectSessionsRepository, + ): List { + return repository.loadSessions(userWalletId).map { it.topic } + } + + fun approveSessionProposal(accounts: List) { + val userNamespaces: Map> = accounts + .groupBy { account -> + blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId) + ?.let { NetworkNamespace(it) } + }.filterNotNull() + walletConnectRepository.approve( + userNamespaces = userNamespaces, + ) + } + + fun rejectSessionProposal() { + walletConnectRepository.reject() + } + + fun disconnectSession(topic: String) { + walletConnectRepository.disconnect(topic) + } + + fun rejectRequest(topic: String, id: Long) { + walletConnectRepository.rejectRequest(topic, id) + } + + private suspend fun handleRequest(sessionRequest: WalletConnectEvents.SessionRequest) { + val error: WalletConnectError? = when { + sessionsRepository.loadSessions(userWalletId).none { it.topic == sessionRequest.topic } -> { + WalletConnectError.WrongUserWallet + } + sessionRequest.request is WcRequest.CustomRequest -> { + WalletConnectError.UnsupportedMethod + } + else -> { + null + } + } + if (error != null) { + walletConnectRepository.rejectRequest(sessionRequest.topic, sessionRequest.id) + return + } + + when (sessionRequest.request) { + is WcRequest.BnbCancel -> Unit + is WcRequest.BnbTxConfirm -> walletConnectRepository.sendRequest( + topic = sessionRequest.topic, + id = sessionRequest.id, + result = "", + ) + else -> { + currentRequest = sessionRequest + val data = prepareRequestData(sessionRequest) + if (data != null) { + handler.onSessionRequest(data) + } + } + } + } + + suspend fun continueWithRequest(request: WcPreparedRequest) { + val currentRequest = this.currentRequest + if (currentRequest == null || request.topic != currentRequest.topic) return + + val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId ?: "") ?: return + val signedHash = when (request) { + is WcPreparedRequest.BnbTransaction -> sdkHelper.signBnbTransaction( + data = request.preparedRequestData.data.data, + networkId = networkId, + derivationPath = request.derivationPath, + cardId = cardId, + ) + is WcPreparedRequest.EthTransaction -> sdkHelper.completeTransaction( + data = request.preparedRequestData, + cardId = cardId, + ) + is WcPreparedRequest.EthSign -> sdkHelper.signPersonalMessage( + hashToSign = request.preparedRequestData.hash, + networkId = networkId, + derivationPath = request.derivationPath, + cardId = cardId, + ) + } + + Timber.d("Signed hash: $signedHash") + + if (signedHash == null) { + walletConnectRepository.rejectRequest( + topic = request.topic, + id = request.requestId, + ) + } else { + walletConnectRepository.sendRequest( + topic = request.topic, + id = request.requestId, + result = signedHash, + ) + } + } + + private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? { + return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt new file mode 100644 index 0000000000..4069a56fa8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.domain.walletconnect2.domain.models.Account +import com.tangem.tap.domain.walletconnect2.domain.models.NetworkNamespace +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectSession +import kotlinx.coroutines.flow.Flow + +interface WalletConnectRepository { + + val events: Flow + + val activeSessions: Flow> + + fun init(projectId: String) + + fun updateSessions() + + fun pair(uri: String) + + fun disconnect(topic: String) + + fun approve(userNamespaces: Map>) + + fun reject() + + fun sendRequest(topic: String, id: Long, result: String) + + fun rejectRequest(topic: String, id: Long) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectSessionsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectSessionsRepository.kt new file mode 100644 index 0000000000..395b928f86 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectSessionsRepository.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.domain.walletconnect2.domain.models.Session + +interface WalletConnectSessionsRepository { + suspend fun loadSessions(userWallet: String): List + + suspend fun saveSession(userWallet: String, session: Session) + + suspend fun removeSession(userWallet: String, topic: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt new file mode 100644 index 0000000000..06f0843223 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcBlockchainHelper.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.domain.walletconnect2.domain + +interface WcBlockchainHelper { + fun chainIdToNetworkIdOrNull(chainId: String): String? + + fun networkIdToChainIdOrNull(networkId: String): String? + + fun getNamespaceFromFullChainIdOrNull(chainId: String): String? + + fun chainIdToFullNameOrNull(chainId: String): String? +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt new file mode 100644 index 0000000000..86ae71084c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt @@ -0,0 +1,209 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.tangem.datasource.di.SdkMoshi +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WCBinanceTxConfirmParam +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceCancelOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +enum class WcJrpcMethods(val code: String) { + + ETH_SIGN("eth_sign"), + ETH_PERSONAL_SIGN("personal_sign"), + ETH_SIGN_TYPE_DATA("eth_signTypedData"), + ETH_SIGN_TYPE_DATA_V4("eth_signTypedData_v4"), + ETH_SIGN_TRANSACTION("eth_signTransaction"), + ETH_SEND_TRANSACTION("eth_sendTransaction"), + BNB_SIGN("bnb_sign"), + BNB_TRANSACTION_CONFIRM("bnb_tx_confirmation"), + SIGN_TRANSACTION("trust_signTransaction"), + ; + + companion object { + fun fromCode(code: String): WcJrpcMethods? = values().firstOrNull { it.code == code } + } +} + +@JsonClass(generateAdapter = true) +data class WCSignTransaction( + val network: Int, + val transaction: String, +) : WcRequestData + +@JsonClass(generateAdapter = true) +data class WcEthereumSignMessage( + val raw: List, + val type: WCSignType, +) : WcRequestData { + enum class WCSignType { + MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE + } + + /** + * Raw parameters will always be the message and the address. Depending on the WCSignType, + * those parameters can be swapped as description below: + * + * - MESSAGE: `[address, data ]` + * - TYPED_MESSAGE: `[address, data]` + * - PERSONAL_MESSAGE: `[data, address]` + * + * reference: https://docs.walletconnect.org/json-rpc/ethereum#eth_signtypeddata + */ + val data + get() = when (type) { + WCSignType.PERSONAL_MESSAGE -> raw[0] + else -> raw[1] + } + + val address + get() = when (type) { + WCSignType.PERSONAL_MESSAGE -> raw[1] + else -> raw[0] + } +} + +@JsonClass(generateAdapter = true) +data class WcEthereumTransaction( + val from: String, + val to: String?, + val nonce: String?, + val gasPrice: String?, + val maxFeePerGas: String?, + val maxPriorityFeePerGas: String?, + val gas: String?, + val gasLimit: String?, + val value: String?, + val data: String, +) : WcRequestData + +interface WcRequestData + +data class WcCustomRequestData(val data: String) : WcRequestData + +sealed class WcRequest(open val data: WcRequestData) { + data class EthSign(override val data: WcEthereumSignMessage) : WcRequest(data) + data class EthSignTransaction(override val data: WcEthereumTransaction) : WcRequest(data) + data class EthSendTransaction(override val data: WcEthereumTransaction) : WcRequest(data) + data class BnbTrade(override val data: WcBinanceTradeOrder) : WcRequest(data) + data class BnbCancel(override val data: WcBinanceCancelOrder) : WcRequest(data) + data class BnbTransfer(override val data: WcBinanceTransferOrder) : WcRequest(data) + data class BnbTxConfirm(override val data: WCBinanceTxConfirmParam) : WcRequest(data) + data class SignTransaction(override val data: WCSignTransaction) : WcRequest(data) + data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data) +} + +@Singleton +class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi: Moshi) { + + @Suppress("ComplexMethod", "LongMethod") + fun deserialize(method: String, params: String): WcRequest { + val customRequest = WcRequest.CustomRequest(WcCustomRequestData(params)) + val wcMethod: WcJrpcMethods = WcJrpcMethods.fromCode(method) ?: return customRequest + + return when (wcMethod) { + WcJrpcMethods.ETH_SIGN_TRANSACTION -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java), + ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.EthSignTransaction(data = deserializedParams) + } + WcJrpcMethods.ETH_SEND_TRANSACTION -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcEthereumTransaction::class.java), + ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.EthSendTransaction(data = deserializedParams) + } + WcJrpcMethods.ETH_SIGN -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, String::class.java), + ).fromJsonOrNull(params) ?: return customRequest + val data = WcEthereumSignMessage( + raw = deserializedParams, + type = WcEthereumSignMessage.WCSignType.MESSAGE, + ) + WcRequest.EthSign(data = data) + } + WcJrpcMethods.ETH_PERSONAL_SIGN -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, String::class.java), + ).fromJsonOrNull(params) ?: return customRequest + val data = WcEthereumSignMessage( + raw = deserializedParams, + type = WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE, + ) + WcRequest.EthSign(data = data) + } + WcJrpcMethods.ETH_SIGN_TYPE_DATA, WcJrpcMethods.ETH_SIGN_TYPE_DATA_V4 -> { + val deserializedParams = listOf( + params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)), + params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1), + ) + val data = WcEthereumSignMessage( + deserializedParams, + WcEthereumSignMessage.WCSignType.TYPED_MESSAGE, + ) + Timber.d("TypedData params: $deserializedParams") + WcRequest.EthSign(data) + } + WcJrpcMethods.BNB_SIGN -> { + return deserializeBnb(moshi, params) ?: customRequest + } + WcJrpcMethods.BNB_TRANSACTION_CONFIRM -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WCBinanceTxConfirmParam::class.java), + ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.BnbTxConfirm(data = deserializedParams) + } + WcJrpcMethods.SIGN_TRANSACTION -> { + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WCSignTransaction::class.java), + ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.SignTransaction(data = deserializedParams) + } + } + } + + private fun deserializeBnb(moshi: Moshi, params: String): WcRequest? { + val cancelOrder = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcBinanceCancelOrder::class.java), + ).fromJsonFirstOrNull(params) + if (cancelOrder != null) return WcRequest.BnbCancel(cancelOrder) + + val tradeOrder = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcBinanceTradeOrder::class.java), + ).fromJsonFirstOrNull(params) + if (tradeOrder != null) return WcRequest.BnbTrade(tradeOrder) + + val transferOrder = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcBinanceTransferOrder::class.java), + ).fromJsonFirstOrNull(params) + if (transferOrder != null) return WcRequest.BnbTransfer(transferOrder) + + return null + } + + private fun JsonAdapter.fromJsonOrNull(data: String): T? { + return try { + this.fromJson(data) + } catch (e: Exception) { + Timber.e(e.message) + null + } + } + + private fun JsonAdapter>.fromJsonFirstOrNull(data: String): T? { + return try { + this.fromJson(data)?.firstOrNull() + } catch (e: Exception) { + Timber.e(e.message) + null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt new file mode 100644 index 0000000000..be17cc867b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.domain.walletconnect2.domain.models.BnbData +import com.tangem.tap.features.details.redux.walletconnect.WcPersonalSignData +import com.tangem.tap.features.details.redux.walletconnect.WcTransactionData + +sealed class WcPreparedRequest( + open val preparedRequestData: Any, + val topic: String, + val requestId: Long, + val derivationPath: String?, +) { + class EthSign( + override val preparedRequestData: WcPersonalSignData, + topic: String, + requestId: Long, + derivationPath: String?, + ) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath) + + class EthTransaction( + override val preparedRequestData: WcTransactionData, + topic: String, + requestId: Long, + derivationPath: String?, + ) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath) + + class BnbTransaction( + override val preparedRequestData: BnbData, + topic: String, + requestId: Long, + derivationPath: String?, + ) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt new file mode 100644 index 0000000000..bc93e31462 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -0,0 +1,136 @@ +package com.tangem.tap.domain.walletconnect2.domain + +import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper +import com.tangem.tap.domain.walletconnect2.domain.models.BnbData +import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType + +class WcSessionRequestConverter( + private val blockchainHelper: WcBlockchainHelper, + private val sessionsRepository: WalletConnectSessionsRepository, + private val sdkHelper: WalletConnectSdkHelper, +) { + + @Suppress("LongMethod") + suspend fun prepareRequest( + sessionRequest: WalletConnectEvents.SessionRequest, + userWalletId: String, + ): WcPreparedRequest? { + val networkId = blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId ?: "") ?: return null + val derivationPath = getDerivationPath( + sessionsRepository = sessionsRepository, + sessionRequest = sessionRequest, + userWalletId = userWalletId, + walletAddress = getWalletAddress(sessionRequest.request), + ) + return when (val request = sessionRequest.request) { + is WcRequest.EthSendTransaction -> { + val data = sdkHelper.prepareTransactionData( + EthTransactionData( + transaction = request.data, + networkId = networkId, + rawDerivationPath = derivationPath, + id = sessionRequest.id, + topic = sessionRequest.topic, + type = WcEthTransactionType.EthSendTransaction, + metaName = sessionRequest.metaName, + metaUrl = sessionRequest.metaUrl, + ), + ) ?: return null + WcPreparedRequest.EthTransaction( + preparedRequestData = data, + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } + is WcRequest.EthSignTransaction -> { + val data = sdkHelper.prepareTransactionData( + EthTransactionData( + transaction = request.data, + networkId = networkId, + rawDerivationPath = derivationPath, + id = sessionRequest.id, + topic = sessionRequest.topic, + type = WcEthTransactionType.EthSignTransaction, + metaName = sessionRequest.metaName, + metaUrl = sessionRequest.metaUrl, + ), + ) ?: return null + WcPreparedRequest.EthTransaction( + preparedRequestData = data, + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } + is WcRequest.EthSign -> { + val data = sdkHelper.prepareDataForPersonalSign( + request.data, + sessionRequest.topic, + sessionRequest.metaName, + sessionRequest.id, + ) + WcPreparedRequest.EthSign( + preparedRequestData = data, + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } + is WcRequest.BnbTrade -> { + val data = sdkHelper.prepareBnbTradeOrder(request.data) + WcPreparedRequest.BnbTransaction( + BnbData( + data = data, + topic = sessionRequest.topic, + requestId = sessionRequest.id, + dAppName = sessionRequest.metaName, + ), + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } + is WcRequest.BnbTransfer -> { + val data = sdkHelper.prepareBnbTransferOrder(request.data) + WcPreparedRequest.BnbTransaction( + BnbData( + data = data, + topic = sessionRequest.topic, + requestId = sessionRequest.id, + dAppName = sessionRequest.metaName, + ), + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } + else -> null + } + } + + private fun getWalletAddress(request: WcRequest): String? { + return when (request) { + is WcRequest.BnbTrade -> request.data.accountNumber + is WcRequest.BnbTransfer -> request.data.accountNumber + is WcRequest.EthSendTransaction -> request.data.from + is WcRequest.EthSignTransaction -> request.data.from + is WcRequest.EthSign -> request.data.address + else -> null + } + } + + private suspend fun getDerivationPath( + sessionsRepository: WalletConnectSessionsRepository, + sessionRequest: WalletConnectEvents.SessionRequest, + userWalletId: String, + walletAddress: String?, + ): String? { + return sessionsRepository.loadSessions(userWalletId) + .firstOrNull { it.topic == sessionRequest.topic } + ?.accounts?.firstOrNull { it.chainId == sessionRequest.chainId && it.walletAddress == walletAddress } + ?.derivationPath + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Account.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Account.kt new file mode 100644 index 0000000000..9372666b46 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Account.kt @@ -0,0 +1,3 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +data class Account(val chainId: String, val walletAddress: String, val derivationPath: String?) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/BnbData.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/BnbData.kt new file mode 100644 index 0000000000..194ca5d01d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/BnbData.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData + +data class BnbData( + val data: BinanceMessageData, + val topic: String, + val requestId: Long, + val dAppName: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/ChainWithDerivation.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/ChainWithDerivation.kt new file mode 100644 index 0000000000..8f43892142 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/ChainWithDerivation.kt @@ -0,0 +1,6 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +data class ChainWithDerivation( + val chain: String, + val derivationPath: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/EthTransactionData.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/EthTransactionData.kt new file mode 100644 index 0000000000..2138d6bd63 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/EthTransactionData.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction +import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType + +data class EthTransactionData( + val transaction: WcEthereumTransaction, + val networkId: String, + val rawDerivationPath: String?, + val id: Long, + val topic: String, + val type: WcEthTransactionType, + val metaName: String, + val metaUrl: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/NetworkNamespace.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/NetworkNamespace.kt new file mode 100644 index 0000000000..a431210e42 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/NetworkNamespace.kt @@ -0,0 +1,4 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +@JvmInline +value class NetworkNamespace(val value: String) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Session.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Session.kt new file mode 100644 index 0000000000..4637a99acb --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/Session.kt @@ -0,0 +1,15 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +data class Session( + val topic: String, + val accounts: List, +) { + companion object { + fun fromAccounts(accounts: List, topic: String): Session { + return Session( + topic = topic, + accounts = accounts, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt new file mode 100644 index 0000000000..1f9b7f6771 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt @@ -0,0 +1,10 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +sealed class WalletConnectError : Exception() { + data class ApprovalErrorMissingNetworks(val missingChains: List) : WalletConnectError() + data class ApprovalErrorAddNetwork(val networks: List) : WalletConnectError() + object ApprovalErrorUnsupportedNetwork : WalletConnectError() + data class ExternalApprovalError(override val message: String?) : WalletConnectError() + object WrongUserWallet : WalletConnectError() + object UnsupportedMethod : WalletConnectError() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectEvents.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectEvents.kt new file mode 100644 index 0000000000..bec06dd554 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectEvents.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +import com.tangem.tap.domain.walletconnect2.domain.WcRequest +import java.net.URI + +sealed interface WalletConnectEvents { + data class SessionProposal( + val name: String, + val description: String, + val url: String, + val icons: List, + val chainIds: List, + ) : WalletConnectEvents + + data class SessionApprovalError(val error: WalletConnectError) : WalletConnectEvents + data class SessionApprovalSuccess(val topic: String, val accounts: List) : WalletConnectEvents + data class SessionDeleted(val topic: String) : WalletConnectEvents + + data class SessionRequest( + val request: WcRequest, + val chainId: String?, + val topic: String, + val id: Long, + val metaName: String, + val metaUrl: String, + ) : WalletConnectEvents +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectSession.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectSession.kt new file mode 100644 index 0000000000..fb535bcc66 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectSession.kt @@ -0,0 +1,8 @@ +package com.tangem.tap.domain.walletconnect2.domain.models + +data class WalletConnectSession( + val topic: String, + val icon: String?, + val name: String?, + val url: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceCancelOrder.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceCancelOrder.kt new file mode 100644 index 0000000000..5d5cc625d2 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceCancelOrder.kt @@ -0,0 +1,35 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.binance + +import com.squareup.moshi.* + +@Suppress("LongParameterList") +@JsonClass(generateAdapter = true) +class WcBinanceCancelOrder( + @Json(name = "account_number") + accountNumber: String, + @Json(name = "chain_id") + chainId: String, + @Json(name = "data") + data: String?, + @Json(name = "memo") + memo: String?, + @Json(name = "sequence") + sequence: String, + @Json(name = "source") + source: String, + @Json(name = "msgs") + msgs: List, +) : WcBinanceOrder(accountNumber, chainId, data, memo, sequence, source, msgs) { + + enum class MessageKey(val key: String) { + REFID("refid"), + SENDER("sender"), + SYMBOL("symbol"), + } + + data class Message( + val refid: String, + val sender: String, + val symbol: String, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt new file mode 100644 index 0000000000..8325aadd72 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt @@ -0,0 +1,19 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.binance + +import com.tangem.tap.domain.walletconnect2.domain.WcRequestData + +@Suppress("LongParameterList") +open class WcBinanceOrder( + val accountNumber: String, + val chainId: String, + val data: String?, + val memo: String?, + val sequence: String, + val source: String, + val msgs: List, +) : WcRequestData + +data class WCBinanceTxConfirmParam( + val ok: Boolean, + val errorMsg: String?, +) : WcRequestData \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradeOrder.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradeOrder.kt new file mode 100644 index 0000000000..35456d2eb5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradeOrder.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.binance + +import com.github.salomonbrys.kotson.* +import com.google.gson.JsonObject +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@Suppress("LongParameterList") +@JsonClass(generateAdapter = true) +class WcBinanceTradeOrder( + @Json(name = "account_number") + accountNumber: String, + @Json(name = "chain_id") + chainId: String, + data: String?, + memo: String?, + sequence: String, + source: String, + msgs: List, +) : WcBinanceOrder(accountNumber, chainId, data, memo, sequence, source, msgs) { + + enum class MessageKey(val key: String) { + ID("id"), + ORDER_TYPE("ordertype"), + PRICE("price"), + QUANTITY("quantity"), + SENDER("sender"), + SIDE("side"), + SYMBOL("symbol"), + TIME_INFORCE("timeinforce"), + } + + data class Message( + val id: String, + val orderType: Int, + val price: Long, + val quantity: Long, + val sender: String, + val side: Int, + val symbol: String, + val timeInforce: Int, + ) +} + +val tradeOrderDeserializer = jsonDeserializer { + WcBinanceTradeOrder.Message( + id = it.json[WcBinanceTradeOrder.MessageKey.ID.key].string, + orderType = it.json[WcBinanceTradeOrder.MessageKey.ORDER_TYPE.key].int, + price = it.json[WcBinanceTradeOrder.MessageKey.PRICE.key].long, + quantity = it.json[WcBinanceTradeOrder.MessageKey.QUANTITY.key].long, + sender = it.json[WcBinanceTradeOrder.MessageKey.SENDER.key].string, + side = it.json[WcBinanceTradeOrder.MessageKey.SIDE.key].int, + symbol = it.json[WcBinanceTradeOrder.MessageKey.SYMBOL.key].string, + timeInforce = it.json[WcBinanceTradeOrder.MessageKey.TIME_INFORCE.key].int, + ) +} + +val tradeOrderSerializer = jsonSerializer { + val jsonObject = JsonObject() + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ID.key, it.src.id) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.ORDER_TYPE.key, it.src.orderType) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.PRICE.key, it.src.price) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.QUANTITY.key, it.src.quantity) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SENDER.key, it.src.sender) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SIDE.key, it.src.side) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.SYMBOL.key, it.src.symbol) + jsonObject.addProperty(WcBinanceTradeOrder.MessageKey.TIME_INFORCE.key, it.src.timeInforce) + + jsonObject +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradePair.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradePair.kt new file mode 100644 index 0000000000..54bf23dcaf --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTradePair.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.binance + +data class WcBinanceTradePair(val from: String, val to: String) { + companion object { + fun from(symbol: String): WcBinanceTradePair? { + val pair = symbol.split("_") + + return if (pair.size > 1) { + val firstParts = pair[0].split("-") + val secondParts = pair[1].split("-") + WcBinanceTradePair(firstParts[0], secondParts[0]) + } else { + null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTransferOrder.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTransferOrder.kt new file mode 100644 index 0000000000..381069071e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceTransferOrder.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.binance + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@Suppress("LongParameterList") +@JsonClass(generateAdapter = true) +class WcBinanceTransferOrder( + @Json(name = "account_number") + accountNumber: String, + @Json(name = "chain_id") + chainId: String, + data: String?, + memo: String?, + sequence: String, + source: String, + msgs: List, +) : WcBinanceOrder(accountNumber, chainId, data, memo, sequence, source, msgs) { + + enum class MessageKey(val key: String) { + INPUTS("inputs"), + OUTPUTS("outputs"), + } + + data class Message( + val inputs: List, + val outputs: List, + ) { + + data class Item( + val address: String, + val coins: List, + ) { + + data class Coin( + val amount: Long, + val denom: String, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt index a43c5a84c4..71c369bcad 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt @@ -67,6 +67,8 @@ internal data class AddCustomTokenChooseTokenBottomSheet( * @property tokenSymbolInputField input field to enter the token symbol * @property decimalsInputField input field to enter the token decimals * @property derivationPathSelectorField selector field to select the derivation path + * @property derivationPathInputField input field for a custom derivation path + * @property showTokenFields if token fields should be shown */ internal data class AddCustomTokenForm( val contractAddressInputField: AddCustomTokenInputField.ContactAddress, @@ -75,6 +77,8 @@ internal data class AddCustomTokenForm( val tokenSymbolInputField: AddCustomTokenInputField.TokenSymbol, val decimalsInputField: AddCustomTokenInputField.Decimals, val derivationPathSelectorField: AddCustomTokenSelectorField.DerivationPath?, + val derivationPathInputField: AddCustomTokenInputField.DerivationPath?, + val showTokenFields: Boolean = false, ) /** Base input field model of add custom token screen */ @@ -174,6 +178,25 @@ internal sealed interface AddCustomTokenInputField { override val placeholder: TextReference, val isEnabled: Boolean, ) : AddCustomTokenInputField + + /** + * Input field model to enter a custom derivation path + * + * @property value current value + * @property onValueChange lambda be invoked when value is been changed + * @property keyboardOptions keyboard options + * @property label label + * @property placeholder placeholder (hint) + * @property showField whether the field should be shown + */ + data class DerivationPath( + override val value: String, + override val onValueChange: (String) -> Unit, + override val keyboardOptions: KeyboardOptions, + override val label: TextReference, + override val placeholder: TextReference, + val showField: Boolean = false, + ) : AddCustomTokenInputField } /** Base selector field model of add custom token screen */ @@ -251,10 +274,15 @@ internal sealed interface AddCustomTokenSelectorField { override val title: TextReference, override val blockchain: Blockchain, val subtitle: TextReference, + val type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN, ) : SelectorItem } } +enum class DerivationPathSelectorType { + DEFAULT, CUSTOM, BLOCKCHAIN +} + /** * Warning model of add custom token screen * @@ -276,6 +304,10 @@ internal sealed class AddCustomTokenWarning(val description: TextReference) { object UnsupportedSolanaToken : AddCustomTokenWarning( description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message), ) + + object WrongDerivationPath : AddCustomTokenWarning( + description = TextReference.Res(R.string.custom_token_invalid_derivation_path), + ) } /** diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt index e463c90ae9..d69f1c08fa 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt @@ -4,14 +4,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import com.tangem.blockchain.common.Blockchain -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning -import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar +import com.tangem.tap.features.customtoken.impl.presentation.models.* import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R @@ -85,6 +78,7 @@ internal object AddCustomTokenPreviewData { onMenuItemClick = {}, isEnabled = true, ), + derivationPathInputField = null, ) } diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt index 618a46fd70..5011ca010c 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt @@ -5,12 +5,11 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton import com.tangem.wallet.R @@ -25,13 +24,13 @@ import com.tangem.wallet.R */ @Composable internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = modifier .imePadding() .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_add), - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, enabled = model.isEnabled, onClick = model.onClick, ) diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt index 6b2e38d732..0051e2e6c4 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt @@ -1,31 +1,11 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.* import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ExposedDropdownMenuBox -import androidx.compose.material.ExposedDropdownMenuDefaults -import androidx.compose.material.LinearProgressIndicator -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester @@ -62,12 +42,13 @@ internal fun AddCustomTokenForm(model: AddCustomTokenForm) { modifier = Modifier.padding(TangemTheme.dimens.spacing16), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { - InputField(model = model.contractAddressInputField) + if (model.showTokenFields) InputField(model = model.contractAddressInputField) SelectorField(model = model.networkSelectorField) - InputField(model = model.tokenNameInputField) - InputField(model = model.tokenSymbolInputField) - InputField(model = model.decimalsInputField) + if (model.showTokenFields) InputField(model = model.tokenNameInputField) + if (model.showTokenFields) InputField(model = model.tokenSymbolInputField) + if (model.showTokenFields) InputField(model = model.decimalsInputField) model.derivationPathSelectorField?.let { SelectorField(model = it) } + if (model.derivationPathInputField?.showField == true) InputField(model = model.derivationPathInputField) } } } @@ -103,6 +84,7 @@ private fun TextField(model: AddCustomTokenInputField, isError: Boolean) { is AddCustomTokenInputField.Decimals -> model.isEnabled is AddCustomTokenInputField.TokenName -> model.isEnabled is AddCustomTokenInputField.TokenSymbol -> model.isEnabled + is AddCustomTokenInputField.DerivationPath -> true } OutlinedTextField( diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt index 74084af835..1d886914c7 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt @@ -15,6 +15,7 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.Token import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.crypto.hdWallet.HDWalletError import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.* @@ -135,18 +136,28 @@ internal class AddCustomTokenViewModel @Inject constructor( tokenSymbolInputField = createTokenSymbolInputField(), decimalsInputField = createDecimalsInputField(), derivationPathSelectorField = createDerivationPathsSelectorField(), + derivationPathInputField = createDerivationPathInputField(), ) } - fun createDerivationPathSelectorItem(blockchain: Blockchain): SelectorItem.TitleWithSubtitle { - return if (blockchain == Blockchain.Unknown) { - SelectorItem.TitleWithSubtitle( + fun createDerivationPathSelectorAdditionalItem( + blockchain: Blockchain, + type: DerivationPathSelectorType = DerivationPathSelectorType.BLOCKCHAIN, + ): SelectorItem.TitleWithSubtitle { + return when (type) { + DerivationPathSelectorType.DEFAULT -> SelectorItem.TitleWithSubtitle( title = TextReference.Res(R.string.custom_token_derivation_path_default), subtitle = TextReference.Res(R.string.custom_token_derivation_path_default), blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, ) - } else { - SelectorItem.TitleWithSubtitle( + DerivationPathSelectorType.CUSTOM -> SelectorItem.TitleWithSubtitle( + title = TextReference.Res(R.string.custom_token_custom_derivation), + subtitle = TextReference.Res(R.string.custom_token_custom_derivation), + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.CUSTOM, + ) + DerivationPathSelectorType.BLOCKCHAIN -> SelectorItem.TitleWithSubtitle( title = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath?.let(TextReference::Str) ?: TextReference.Res(R.string.custom_token_derivation_path_default), subtitle = TextReference.Str(blockchain.fullName), @@ -175,7 +186,7 @@ internal class AddCustomTokenViewModel @Inject constructor( onValueChange = actionsHandler::onContactAddressValueChange, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), label = TextReference.Res(R.string.custom_token_contract_address_input_title), - placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"), + placeholder = TextReference.Str(value = CONTRACT_ADDRESS_PLACEHOLDER), isLoading = false, isError = false, error = null, @@ -196,8 +207,7 @@ internal class AddCustomTokenViewModel @Inject constructor( val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown) return listOf(defaultNetwork) + Blockchain.values() .filter { blockchain -> - (blockchain.isEvm() || blockchain.canHandleTokens()) && - reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true + reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true } .sortedBy(Blockchain::fullName) .map(::createNetworkSelectorItem) @@ -231,7 +241,7 @@ internal class AddCustomTokenViewModel @Inject constructor( onValueChange = actionsHandler::onDecimalsValueChange, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next), label = TextReference.Res(R.string.custom_token_decimals_input_title), - placeholder = TextReference.Str(value = "8"), + placeholder = TextReference.Str(value = DECIMALS_PLACEHOLDER), isEnabled = false, ) } @@ -250,11 +260,31 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun getDerivationPathsSelectorItems(): List { - val defaultDerivationPath = createDerivationPathSelectorItem(Blockchain.Unknown) - return listOf(defaultDerivationPath) + Blockchain.values() - .filter { blockchain -> blockchain.isEvm() && blockchain.isSupportedInApp() && !blockchain.isTestnet() } + return listOf( + createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, + ), + createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.CUSTOM, + ), + ) + Blockchain.values() + .filter { blockchain -> blockchain.isSupportedInApp() && !blockchain.isTestnet() } .sortedBy(Blockchain::fullName) - .map(::createDerivationPathSelectorItem) + .map(::createDerivationPathSelectorAdditionalItem) + } + + private fun createDerivationPathInputField(): AddCustomTokenInputField.DerivationPath? { + if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null + + return AddCustomTokenInputField.DerivationPath( + value = "", + onValueChange = actionsHandler::onDerivationPathValueChange, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + label = TextReference.Res(R.string.custom_token_custom_derivation), + placeholder = TextReference.Str(value = DERIVATION_PATH_PLACEHOLDER), + ) } } @@ -306,25 +336,16 @@ internal class AddCustomTokenViewModel @Inject constructor( Timber.e(it) } - updateDerivationPathSelector() updateWarnings() updateFloatingButton() } } - private fun updateDerivationPathSelector() { - uiState = uiState.copySealed( - form = uiState.form.copy( - derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy( - isEnabled = uiState.form.networkSelectorField.selectedItem.blockchain.isEvm(), - ), - ), - ) - } - private fun isDerivationPathSelected(): Boolean { return with(uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) { - this != null && this != Blockchain.Unknown + this != null && this != Blockchain.Unknown || + uiState.form.derivationPathSelectorField?.selectedItem?.type == DerivationPathSelectorType.CUSTOM && + !uiState.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) } } @@ -397,6 +418,7 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun updateFloatingButton() { + uiState = updateStateWithDerivationError(uiState) if (isCustomTokenAlreadyAdded()) { uiState = uiState.copySealed( warnings = uiState.warnings + AddCustomTokenWarning.TokenAlreadyAdded, @@ -405,6 +427,7 @@ internal class AddCustomTokenViewModel @Inject constructor( return } + val isCorrectDerivationInput = !uiState.warnings.contains(AddCustomTokenWarning.WrongDerivationPath) val state = when { isAllTokenFieldsFilled() && isNetworkSelected() -> { val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain @@ -418,7 +441,8 @@ internal class AddCustomTokenViewModel @Inject constructor( uiState.copySealed( floatingButton = uiState.floatingButton.copy( - isEnabled = error is ContractAddressValidatorResult.Success && isSupportedToken, + isEnabled = error is ContractAddressValidatorResult.Success && + isSupportedToken && isCorrectDerivationInput, ), ) } @@ -430,17 +454,43 @@ internal class AddCustomTokenViewModel @Inject constructor( else -> { uiState.copySealed( floatingButton = uiState.floatingButton.copy( - isEnabled = if (isNetworkSelected()) !isBlockchainAlreadyAdded() else false, + isEnabled = if (isNetworkSelected()) { + !isBlockchainAlreadyAdded() && isCorrectDerivationInput + } else { + false + }, ), ) } } uiState = state.copySealed( - warnings = uiState.warnings - AddCustomTokenWarning.TokenAlreadyAdded, + warnings = state.warnings - AddCustomTokenWarning.TokenAlreadyAdded, ) } + private fun updateStateWithDerivationError(uiState: AddCustomTokenStateHolder): AddCustomTokenStateHolder { + val updatedWarnings = if (isWrongDerivationPathEntered( + derivationPathSelectorType = uiState.form.derivationPathSelectorField?.selectedItem?.type, + derivationPath = getDerivationPath(), + ) + ) { + uiState.warnings + AddCustomTokenWarning.WrongDerivationPath + } else { + uiState.warnings - AddCustomTokenWarning.WrongDerivationPath + } + return uiState.copySealed( + warnings = updatedWarnings, + ) + } + + private fun isWrongDerivationPathEntered( + derivationPathSelectorType: DerivationPathSelectorType?, + derivationPath: DerivationPath?, + ): Boolean { + return derivationPathSelectorType == DerivationPathSelectorType.CUSTOM && derivationPath == null + } + private fun isCustomTokenAlreadyAdded(): Boolean { return when (getCustomTokenType()) { CustomTokenType.TOKEN -> isTokenAlreadyAdded() @@ -520,16 +570,36 @@ internal class AddCustomTokenViewModel @Inject constructor( } private fun getDerivationPath(): DerivationPath? { + return when (uiState.form.derivationPathSelectorField?.selectedItem?.type) { + DerivationPathSelectorType.CUSTOM -> + createDerivationPathOrNull(uiState.form.derivationPathInputField?.value ?: "") + else -> + getDerivationPathForBlockchain(uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) + } + } + + private fun createDerivationPathOrNull(rawPath: String): DerivationPath? { + return try { + DerivationPath(rawPath) + } catch (error: HDWalletError) { + null + } + } + + private fun getDerivationPathForBlockchain(blockchain: Blockchain?): DerivationPath? { + if (blockchain == null) return null + val derivationStyle = if (!isDerivationPathSelected()) { reduxStateHolder.scanResponse?.card?.derivationStyle } else { DerivationStyle.LEGACY } - - return when (val derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) { - Blockchain.Unknown -> uiState.form.networkSelectorField.selectedItem.blockchain - else -> derivationNetwork - }?.derivationPath(derivationStyle) + val derivationNetwork = if (blockchain == Blockchain.Unknown) { + uiState.form.networkSelectorField.selectedItem.blockchain + } else { + blockchain + } + return derivationNetwork.derivationPath(derivationStyle) } private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) { @@ -566,7 +636,6 @@ internal class AddCustomTokenViewModel @Inject constructor( is ContractAddressValidatorResult.Error -> { handleContractAddressErrorValidation(type = validatorResult.type) - updateDerivationPathSelector() updateWarnings() updateFloatingButton() } @@ -574,11 +643,13 @@ internal class AddCustomTokenViewModel @Inject constructor( } fun onNetworkSelectorItemClick(index: Int) { + val selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)) uiState = uiState.copySealed( form = uiState.form.copy( networkSelectorField = uiState.form.networkSelectorField.copy( - selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)), + selectedItem = selectedItem, ), + showTokenFields = selectedItem.blockchain.canHandleTokens(), ), ) onContactAddressValueChange(uiState.form.contractAddressInputField.value) @@ -612,12 +683,26 @@ internal class AddCustomTokenViewModel @Inject constructor( } fun onDerivationPathSelectorItemClick(index: Int) { - val field = requireNotNull(uiState.form.derivationPathSelectorField) + val derivationSelector = requireNotNull(uiState.form.derivationPathSelectorField) + val selected = requireNotNull(derivationSelector.items.getOrNull(index)) + val derivationInputField = requireNotNull(uiState.form.derivationPathInputField) uiState = uiState.copySealed( form = uiState.form.copy( - derivationPathSelectorField = field.copy( - selectedItem = requireNotNull(field.items.getOrNull(index)), + derivationPathSelectorField = derivationSelector.copy( + selectedItem = selected, ), + derivationPathInputField = derivationInputField.copy( + showField = selected.type == DerivationPathSelectorType.CUSTOM, + ), + ), + ) + updateFloatingButton() + } + + fun onDerivationPathValueChange(enteredValue: String) { + uiState = uiState.copySealed( + form = uiState.form.copy( + derivationPathInputField = uiState.form.derivationPathInputField?.copy(value = enteredValue), ), ) updateFloatingButton() @@ -691,7 +776,11 @@ internal class AddCustomTokenViewModel @Inject constructor( decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false), derivationPathSelectorField = derivationPathSelectorField?.copy( isEnabled = true, - selectedItem = formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown), + selectedItem = formStateBuilder + .createDerivationPathSelectorAdditionalItem( + blockchain = Blockchain.Unknown, + type = DerivationPathSelectorType.DEFAULT, + ), ), ), ) @@ -722,5 +811,8 @@ internal class AddCustomTokenViewModel @Inject constructor( TestTokenItem(name = "USDT (invalid - 1/3 of address)", address = "Es9vMFrzaCERmJ"), TestTokenItem(name = "ETH (full)", address = "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk"), ) + const val DERIVATION_PATH_PLACEHOLDER = "m/44'/0'/0'/0/0" + const val DECIMALS_PLACEHOLDER = "8" + const val CONTRACT_ADDRESS_PLACEHOLDER = "0x0000000000000000000000000000000000000000" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt index 0a4ad3b4a6..baa487cec7 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt @@ -1,48 +1,21 @@ package com.tangem.tap.features.customtoken.legacy.compose -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.BottomSheetScaffold -import androidx.compose.material.BottomSheetScaffoldState -import androidx.compose.material.BottomSheetState -import androidx.compose.material.BottomSheetValue -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FabPosition -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.material.Surface -import androidx.compose.material.rememberBottomSheetScaffoldState -import androidx.compose.material.rememberScaffoldState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.AddCustomTokenError import com.tangem.domain.common.form.DataField import com.tangem.domain.common.form.FieldId -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.ContractAddress -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Decimals -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.DerivationPath -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Name -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Network -import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.Symbol +import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.* import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState import com.tangem.domain.features.addCustomToken.redux.ScreenState @@ -189,12 +162,12 @@ fun Warnings(warnings: List) { @Composable private fun AddButton(state: MutableState) { - PrimaryButtonIconLeft( + PrimaryButtonIconStart( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(id = R.string.common_add), - icon = painterResource(id = R.drawable.ic_plus_24), + iconResId = R.drawable.ic_plus_24, enabled = state.value.screenState.addButton.isEnabled, onClick = { domainStore.dispatch(AddCustomTokenAction.OnAddCustomTokenClicked) }, ) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index 85e80df897..e28b9cd398 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -98,7 +98,7 @@ private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesRe } } val allowedSecurityOptions = when { - cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() || cardTypesResolver.isSaltPay() -> { + cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> { EnumSet.of(SecurityOption.LongTap) } card.settings.isBackupAllowed -> { @@ -116,7 +116,7 @@ private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesRe private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean { val hasPermanentWallet = card.wallets.any { it.settings.isPermanent } - val isNotAllowed = hasPermanentWallet || cardTypesResolver.isSaltPay() || cardTypesResolver.isStart2Coin() + val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin() return !isNotAllowed } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 9afb96f10f..6a773800b6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -4,6 +4,10 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.redux.NotificationAction import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.walletconnect.Topic +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError +import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.wallet.R import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder @@ -58,7 +62,7 @@ sealed class WalletConnectAction : Action { data class SetSessionsRestored(val sessions: List) : WalletConnectAction() - data class DisconnectSession(val session: WCSession) : WalletConnectAction() + data class DisconnectSession(val topic: String, val session: WCSession?) : WalletConnectAction() data class RemoveSession(val session: WCSession) : WalletConnectAction() @@ -66,7 +70,7 @@ sealed class WalletConnectAction : Action { val transaction: WCEthereumTransaction, val session: WalletConnectSession, val id: Long, - val type: WcTransactionType, + val type: WcEthTransactionType, ) : WalletConnectAction() @@ -78,11 +82,11 @@ sealed class WalletConnectAction : Action { val id: Long, ) : WalletConnectAction() - data class SendTransaction(val session: WCSession) : WalletConnectAction() + data class SendTransaction(val topic: Topic) : WalletConnectAction() - data class SignMessage(val session: WCSession) : WalletConnectAction() + data class SignMessage(val topic: Topic) : WalletConnectAction() - data class RejectRequest(val session: WCSession, val id: Long) : WalletConnectAction() + data class RejectRequest(val topic: Topic, val id: Long) : WalletConnectAction() object NotEnoughFunds : WalletConnectAction(), NotificationAction { override val messageResource = R.string.wallet_connect_create_tx_not_enough_funds @@ -108,7 +112,22 @@ sealed class WalletConnectAction : Action { data class Sign( val id: Long, val data: ByteArray, - val sessionData: WCSession, + val topic: Topic, ) : WalletConnectAction() } + + //region WalletConnect 2.0 + object ApproveProposal : WalletConnectAction() + object RejectProposal : WalletConnectAction() + + object SessionEstablished : WalletConnectAction() + data class SessionRejected(val error: WalletConnectError) : WalletConnectAction() + data class SessionListUpdated(val sessions: List) : WalletConnectAction() + + data class ShowSessionRequest(val sessionRequest: WcPreparedRequest) : WalletConnectAction() + + object RejectUnsupportedRequest : WalletConnectAction() + + data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction() + //endregion WalletConnect 2.0 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 65dfeb1eab..b83b2ad75a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -5,8 +5,10 @@ import com.tangem.blockchain.common.DerivationStyle import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.guard import com.tangem.domain.common.TapWorkarounds.derivationStyle -import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.common.extensions.fromNetworkId +import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain @@ -19,8 +21,16 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.domain.walletconnect.BnbHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils +import com.tangem.tap.domain.walletconnect.extensions.toWcEthTransaction +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.models.Account +import com.tangem.tap.domain.walletconnect2.domain.models.BnbData +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.wallet.R @@ -29,8 +39,14 @@ import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber +@Suppress("LargeClass") class WalletConnectMiddleware { private var walletConnectManager = WalletConnectManager() + private val walletConnectInteractor: WalletConnectInteractor + get() = store.state.daggerGraphState.get(DaggerGraphState::walletConnectInteractor) + private val walletConnectRepository: WalletConnectRepository + get() = store.state.daggerGraphState.get(DaggerGraphState::walletConnectRepository) + val walletConnectMiddleware: Middleware = { dispatch, state -> { next -> { action -> @@ -115,9 +131,13 @@ class WalletConnectMiddleware { store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedCard)) } is WalletConnectAction.OpenSession -> { - walletConnectManager.connect( - wcUri = action.wcUri, - ) + val index = action.wcUri.indexOf("@") + when (action.wcUri[index + 1]) { + '1' -> { + walletConnectManager.connect(wcUri = action.wcUri) + } + '2' -> walletConnectRepository.pair(uri = action.wcUri) + } } is WalletConnectAction.RefuseOpeningSession -> { store.dispatch( @@ -134,11 +154,15 @@ class WalletConnectMiddleware { walletConnectManager.approve(action.session) } is WalletConnectAction.DisconnectSession -> { - walletConnectManager.disconnect(action.session) + if (action.session != null) { + walletConnectManager.disconnect(action.session) + } else { + walletConnectInteractor.disconnectSession(action.topic) + } } is WalletConnectAction.HandleTransactionRequest -> { walletConnectManager.handleTransactionRequest( - transaction = action.transaction, + transaction = action.transaction.toWcEthTransaction(), session = action.session, id = action.id, type = action.type, @@ -152,23 +176,31 @@ class WalletConnectMiddleware { ) } is WalletConnectAction.RejectRequest -> { - walletConnectManager.rejectRequest(action.session, action.id) + walletConnectManager.rejectRequest(action.topic, action.id) + walletConnectInteractor.rejectRequest(action.topic, action.id) } is WalletConnectAction.SendTransaction -> { - walletConnectManager.completeTransaction(action.session) + walletConnectManager.completeTransaction(action.topic) } is WalletConnectAction.SignMessage -> { - walletConnectManager.sendSignedMessage(action.session) + walletConnectManager.sendSignedMessage(action.topic) } is WalletConnectAction.BinanceTransaction.Trade -> { val messageData = BnbHelper.createMessageData(action.order) store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.BnbTransactionDialog( - data = messageData, - session = action.sessionData.session, - sessionId = action.id, - dAppName = action.sessionData.peerMeta.name, + WcPreparedRequest.BnbTransaction( + preparedRequestData = BnbData( + data = messageData, + topic = action.sessionData.session.topic, + requestId = action.id, + dAppName = action.sessionData.peerMeta.name, + ), + topic = action.sessionData.session.topic, + requestId = action.id, + derivationPath = action.sessionData.wallet.derivationPath?.rawPath, + ), ), ), ) @@ -178,10 +210,17 @@ class WalletConnectMiddleware { store.dispatchOnMain( GlobalAction.ShowDialog( WalletConnectDialog.BnbTransactionDialog( - data = messageData, - session = action.sessionData.session, - sessionId = action.id, - dAppName = action.sessionData.peerMeta.name, + WcPreparedRequest.BnbTransaction( + preparedRequestData = BnbData( + data = messageData, + topic = action.sessionData.session.topic, + requestId = action.id, + dAppName = action.sessionData.peerMeta.name, + ), + topic = action.sessionData.session.topic, + requestId = action.id, + derivationPath = action.sessionData.wallet.derivationPath?.rawPath, + ), ), ), ) @@ -190,7 +229,7 @@ class WalletConnectMiddleware { walletConnectManager.signBnb( id = action.id, data = action.data, - sessionData = action.sessionData, + topic = action.topic, ) } is WalletConnectAction.SwitchBlockchain -> { @@ -226,6 +265,70 @@ class WalletConnectMiddleware { is WalletConnectAction.UpdateBlockchain -> { walletConnectManager.updateBlockchain(action.updatedSession) } + is WalletConnectAction.ApproveProposal -> { + val accounts = store.state.walletState.walletManagers + .mapNotNull { + val wallet = it.wallet + val chainId = walletConnectInteractor.blockchainHelper.networkIdToChainIdOrNull( + wallet.blockchain.toNetworkId(), + ) + chainId?.let { + Account( + chainId, + wallet.address, + wallet.publicKey.derivationPath?.rawPath, + ) + } + } + + walletConnectInteractor.approveSessionProposal(accounts) + } + is WalletConnectAction.RejectProposal -> { + walletConnectInteractor.rejectSessionProposal() + } + is WalletConnectAction.SessionEstablished -> { + } + is WalletConnectAction.SessionRejected -> { + when (action.error) { + is WalletConnectError.ApprovalErrorAddNetwork -> { + val blockchains = action.error.networks.mapNotNull { Blockchain.fromNetworkId(it)?.fullName } + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.AddNetwork(blockchains.toString()), + ), + ) + } + + WalletConnectError.ApprovalErrorUnsupportedNetwork -> { + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + } + is WalletConnectError.ExternalApprovalError -> { + store.dispatchOnMain( + GlobalAction.ShowDialog( + AppDialog.SimpleOkWarningDialog( + message = action.error.message ?: "", + ), + ), + ) + } + else -> Unit + } + } + + is WalletConnectAction.ShowSessionRequest -> { + val dialog: WalletConnectDialog = when (val request = action.sessionRequest) { + is WcPreparedRequest.BnbTransaction -> WalletConnectDialog.BnbTransactionDialog(request) + is WcPreparedRequest.EthTransaction -> WalletConnectDialog.RequestTransaction(request) + is WcPreparedRequest.EthSign -> WalletConnectDialog.PersonalSign(request) + } + store.dispatch(GlobalAction.ShowDialog(dialog)) + } + is WalletConnectAction.PerformRequestedAction -> { + scope.launch { walletConnectInteractor.continueWithRequest(action.sessionRequest) } + } + is WalletConnectAction.RejectUnsupportedRequest -> { + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + } } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index ed1573f3bb..44130862c0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -27,15 +27,23 @@ object WalletConnectReducer { state.sessions.filterNot { it.session.toUri() == action.session.toUri() } state.copy(sessions = sessions) } - is WalletConnectAction.UnsupportedCard -> state.copy(loading = false) - is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) - is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) - is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) + is WalletConnectAction.UnsupportedCard, + is WalletConnectAction.RefuseOpeningSession, + is WalletConnectAction.OpeningSessionTimeout, + is WalletConnectAction.FailureEstablishingSession, + -> state.copy(loading = false) is WalletConnectAction.UpdateBlockchain -> state.copy( sessions = state.sessions .filterNot { it.peerId == action.updatedSession.peerId } + action.updatedSession, ) - + is WalletConnectAction.ApproveProposal -> state.copy(loading = true) + is WalletConnectAction.RejectProposal, + is WalletConnectAction.SessionEstablished, + is WalletConnectAction.SessionRejected, + -> state.copy(loading = false) + is WalletConnectAction.SessionListUpdated -> state.copy( + wc2Sessions = action.sessions, + ) else -> state } } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 11bb3e6a8b..71f4a14ca6 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -8,6 +8,10 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.trustwallet.walletconnect.models.WCPeerMeta @@ -16,6 +20,7 @@ import com.trustwallet.walletconnect.models.session.WCSession data class WalletConnectState( val loading: Boolean = false, val sessions: List = listOf(), + val wc2Sessions: List = listOf(), val newSessionData: NewWcSessionData? = null, ) @@ -95,43 +100,47 @@ sealed class WalletConnectDialog : StateDialog { val networks: List, ) : WalletConnectDialog() + data class SessionProposalDialog( + val sessionProposal: WalletConnectEvents.SessionProposal, + val networks: String, + val onApprove: () -> Unit, + val onReject: () -> Unit, + ) : WalletConnectDialog() + data class ChooseNetwork( val session: WalletConnectSession, val networks: List, ) : WalletConnectDialog() - data class RequestTransaction(val dialogData: TransactionRequestDialogData) : + data class RequestTransaction(val data: WcPreparedRequest.EthTransaction) : WalletConnectDialog() - data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog() + data class PersonalSign(val data: WcPreparedRequest.EthSign) : WalletConnectDialog() data class BnbTransactionDialog( - val data: BinanceMessageData, - val session: WCSession, - val sessionId: Long, - val dAppName: String, + val data: WcPreparedRequest.BnbTransaction, ) : WalletConnectDialog() } data class WcTransactionData( - val type: WcTransactionType, + val type: WcEthTransactionType, val transaction: TransactionData, - val session: WalletConnectSession, + val topic: String, val id: Long, val walletManager: WalletManager, val dialogData: TransactionRequestDialogData, ) -enum class WcTransactionType { +enum class WcEthTransactionType { EthSignTransaction, EthSendTransaction, } data class WcPersonalSignData( val hash: ByteArray, - val session: WalletConnectSession, + val topic: String, val id: Long, val dialogData: PersonalSignDialogData, - + val type: WcEthereumSignMessage.WCSignType, ) sealed class BinanceMessageData( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 38fdbe45f6..c92047d0bc 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -1,23 +1,9 @@ package com.tangem.tap.features.details.ui.common import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.selection.selectable -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.RadioButton -import androidx.compose.material.RadioButtonDefaults -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -25,7 +11,7 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.PrimaryButtonIconRight +import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -110,13 +96,13 @@ fun EmptyTopBarWithNavigation( @Composable fun DetailsMainButton(title: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) { - PrimaryButtonIconRight( + PrimaryButtonIconEnd( text = title, enabled = enabled, onClick = onClick, modifier = modifier .fillMaxWidth(), - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt index 10edfda464..e9766c6147 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsViewModel.kt @@ -32,13 +32,8 @@ class DetailsViewModel(private val store: Store) { SettingsElement.WalletConnect -> { if (cardTypesResolver?.isMultiwalletAllowed() == true) it else null } - SettingsElement.SendFeedback -> - if (cardTypesResolver?.isSaltPay() != true) it else null - SettingsElement.LinkMoreCards -> { - // if (state.createBackupAllowed) it else null - // TODO: SaltPay: temporary excluding backup process for Visa cards - if (state.createBackupAllowed && cardTypesResolver?.isSaltPay() != true) it else null - } + SettingsElement.SendFeedback -> it + SettingsElement.LinkMoreCards -> if (state.createBackupAllowed) it else null SettingsElement.PrivacyPolicy -> { if (state.privacyPolicyUrl != null) it else null } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt index ba234154d5..c9f4292991 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreen.kt @@ -1,24 +1,11 @@ package com.tangem.tap.features.details.ui.walletconnect import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.FloatingActionButton -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.LinearProgressIndicator -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -36,6 +23,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.extensions.getFromClipboard import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold import com.tangem.wallet.R +import kotlinx.collections.immutable.persistentListOf @Composable fun WalletConnectScreen(state: WalletConnectScreenState, onBackClick: () -> Unit) { @@ -164,7 +152,7 @@ private fun WalletConnectSessions(state: WalletConnectScreenState) { private fun WalletConnectScreenPreview() { WalletConnectScreen( state = WalletConnectScreenState( - sessions = listOf( + sessions = persistentListOf( WcSessionForScreen( description = "session from some dApp", sessionId = "", diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt index e61b4c22e6..24878f3183 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectScreenState.kt @@ -1,9 +1,10 @@ package com.tangem.tap.features.details.ui.walletconnect import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession +import kotlinx.collections.immutable.ImmutableList data class WalletConnectScreenState( - val sessions: List, + val sessions: ImmutableList, val isLoading: Boolean = false, val onRemoveSession: (String) -> Unit = {}, val onAddSession: (String?) -> Unit = {}, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt index 6bbde4c1ae..4707547c51 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/WalletConnectViewModel.kt @@ -4,23 +4,35 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState +import kotlinx.collections.immutable.toImmutableList import org.rekotlin.Store +import timber.log.Timber class WalletConnectViewModel(private val store: Store) { fun updateState(state: WalletConnectState): WalletConnectScreenState { + Timber.d("WC2 Sessions: ${state.wc2Sessions}") + val sessions = state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) } + state.wc2Sessions return WalletConnectScreenState( - state.sessions.map { wcSession -> WcSessionForScreen.fromSession(wcSession) }, + sessions.toImmutableList(), isLoading = state.loading, - onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions) }, + onRemoveSession = { sessionUri -> onRemoveSession(sessionUri, state.sessions, state.wc2Sessions) }, onAddSession = { copiedUri -> store.dispatch(WalletConnectAction.StartWalletConnect(copiedUri)) }, ) } - private fun onRemoveSession(sessionUri: String, sessions: List) { + private fun onRemoveSession( + sessionUri: String, + sessions: List, + wc2sessions: List, + ) { sessions .firstOrNull { it.session.toUri() == sessionUri } ?.let { baseSession -> - store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session)) + store.dispatch(WalletConnectAction.DisconnectSession(baseSession.session.topic, baseSession.session)) + return } + wc2sessions.firstOrNull { it.sessionId == sessionUri }?.let { + store.dispatch(WalletConnectAction.DisconnectSession(sessionUri, null)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt index 3d8c70a937..27b3abdb23 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -3,20 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.store import com.tangem.wallet.R -import com.trustwallet.walletconnect.models.session.WCSession object BnbTransactionDialog { - fun create( - data: BinanceMessageData, - session: WCSession, - sessionId: Long, - dAppName: String, - context: Context, - ): AlertDialog { + fun create(preparedData: WcPreparedRequest.BnbTransaction, context: Context): AlertDialog { + val data = preparedData.preparedRequestData.data val message = when (data) { is BinanceMessageData.Trade -> data.tradeData.map { context.getString( @@ -37,7 +32,7 @@ object BnbTransactionDialog { val fullMessage = context.getString( R.string.wallet_connect_bnb_sign_message, - dAppName, + preparedData.preparedRequestData.dAppName, message, ) val positiveButtonTitle = context.getText(R.string.common_sign) @@ -48,14 +43,15 @@ object BnbTransactionDialog { setPositiveButton(positiveButtonTitle) { _, _ -> store.dispatch( WalletConnectAction.BinanceTransaction.Sign( - id = sessionId, + id = preparedData.requestId, data = data.data, - sessionData = session, + topic = preparedData.topic, ), ) + store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) + store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId)) } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt index 5f6bf384ed..2e354e9f0a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/PersonalSignDialog.kt @@ -3,22 +3,24 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction import com.tangem.tap.store import com.tangem.wallet.R -import com.trustwallet.walletconnect.models.session.WCSession object PersonalSignDialog { - fun create(data: PersonalSignDialogData, context: Context): AlertDialog { + fun create(preparedData: WcPreparedRequest.EthSign, context: Context): AlertDialog { + val data = preparedData.preparedRequestData.dialogData val message = context.getString(R.string.wallet_connect_alert_sign_message, data.message) return AlertDialog.Builder(context).apply { setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> - store.dispatch(WalletConnectAction.SignMessage(data.session)) + store.dispatch(WalletConnectAction.SignMessage(data.topic)) + store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id)) } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) @@ -30,6 +32,6 @@ object PersonalSignDialog { data class PersonalSignDialogData( val dAppName: String, val message: String, - val session: WCSession, + val topic: String, val id: Long, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SessionProposalDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SessionProposalDialog.kt new file mode 100644 index 0000000000..ee1ab06996 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SessionProposalDialog.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.features.details.ui.walletconnect.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents +import com.tangem.tap.store +import com.tangem.wallet.R + +object SessionProposalDialog { + fun create( + sessionProposal: WalletConnectEvents.SessionProposal, + networks: String, + context: Context, + onApprove: () -> Unit, + onReject: () -> Unit, + ): AlertDialog { + val message = context.getString( + R.string.wallet_connect_request_session_start, + sessionProposal.name, + networks, + sessionProposal.url, + ) + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_start)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + onApprove() + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(GlobalAction.HideDialog) + onReject() + } + setOnCancelListener { + store.dispatch(GlobalAction.HideDialog) + onReject() + } + }.create() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt index 4d502f7275..21ca09c15c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/TransactionDialog.kt @@ -3,14 +3,15 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction -import com.tangem.tap.features.details.redux.walletconnect.WcTransactionType +import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType import com.tangem.tap.store import com.tangem.wallet.R -import com.trustwallet.walletconnect.models.session.WCSession object TransactionDialog { - fun create(data: TransactionRequestDialogData, context: Context): AlertDialog { + fun create(preparedData: WcPreparedRequest.EthTransaction, context: Context): AlertDialog { + val data = preparedData.preparedRequestData.dialogData val message = context.getString( R.string.wallet_connect_create_tx_message, data.dAppName, @@ -22,22 +23,23 @@ object TransactionDialog { ) val positiveButtonTitle = when (data.type) { - WcTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) - WcTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) + WcEthTransactionType.EthSignTransaction -> context.getText(R.string.common_sign) + WcEthTransactionType.EthSendTransaction -> context.getText(R.string.common_sign_and_send) } return AlertDialog.Builder(context).apply { setTitle(context.getString(R.string.wallet_connect_title)) setMessage(message) setPositiveButton(positiveButtonTitle) { _, _ -> if (data.isEnoughFundsToSend) { - store.dispatch(WalletConnectAction.SendTransaction(data.session)) + store.dispatch(WalletConnectAction.SendTransaction(data.topic)) + store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) } else { - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id)) store.dispatch(WalletConnectAction.NotEnoughFunds) } } setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> - store.dispatch(WalletConnectAction.RejectRequest(data.session, data.id)) + store.dispatch(WalletConnectAction.RejectRequest(data.topic, data.id)) } setOnDismissListener { store.dispatch(GlobalAction.HideDialog) @@ -54,7 +56,7 @@ data class TransactionRequestDialogData( val totalAmount: String, val balance: String, val isEnoughFundsToSend: Boolean, - val session: WCSession, + val topic: String, val id: Long, - val type: WcTransactionType, + val type: WcEthTransactionType, ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt index 27dd88ee13..6f09751342 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/Disclaimer.kt @@ -40,12 +40,6 @@ class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(da override fun getPreferenceKey(): String = "tangem_tos_accepted" } -class SaltPayDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { - override fun type(): DisclaimerType = DisclaimerType.SaltPay - override fun getUri(): Uri = Uri.parse("$baseUrl/soltpay_tos.html") - override fun getPreferenceKey(): String = "saltPay_tos_accepted" -} - class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) { override fun type(): DisclaimerType = DisclaimerType.Start2Coin override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion())) diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt index 56519de4ff..b01b2b6f60 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.disclaimer -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.models.scan.CardDTO import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage @@ -12,14 +11,12 @@ import java.util.Locale */ enum class DisclaimerType { Tangem, - SaltPay, Start2Coin, ; companion object { fun get(cardDTO: CardDTO): DisclaimerType { return when { - cardDTO.isSaltPay -> SaltPay cardDTO.isStart2Coin -> Start2Coin else -> Tangem } @@ -31,7 +28,6 @@ fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer { val dataProvider = provideDisclaimerDataProvider(cardDTO.cardId) return when (this) { DisclaimerType.Tangem -> TangemDisclaimer(dataProvider) - DisclaimerType.SaltPay -> SaltPayDisclaimer(dataProvider) DisclaimerType.Start2Coin -> Start2CoinDisclaimer(dataProvider) } } diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 62a0161bb8..c54f4ff005 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.disclaimer.ui import android.os.Bundle import android.view.View import android.view.View.OVER_SCROLL_NEVER +import android.webkit.WebView import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.core.ui.fragments.setStatusBarColor @@ -27,21 +28,6 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind) private val webViewClient = DisclaimerWebViewClient() - override fun configureTransitions() { - val inflater = TransitionInflater.from(requireContext()) - when (store.state.disclaimerState.showedFromScreen) { - AppScreen.Home -> { - enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) - exitTransition = inflater.inflateTransition(android.R.transition.slide_top) - } - AppScreen.Details -> { - enterTransition = inflater.inflateTransition(android.R.transition.fade) - exitTransition = inflater.inflateTransition(android.R.transition.fade) - } - else -> {} - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(handler = this) @@ -86,11 +72,29 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs super.onStop() } + override fun configureTransitions() { + val inflater = TransitionInflater.from(requireContext()) + when (store.state.disclaimerState.showedFromScreen) { + AppScreen.Home -> { + enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) + exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + } + AppScreen.Details -> { + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + else -> {} + } + } + + override fun handleOnBackPressed() { + store.dispatch(DisclaimerAction.OnBackPressed) + } + override fun newState(state: DisclaimerState) = with(binding) { if (activity == null || view == null) return updateUiVisibility(state.disclaimer, state.progressState) - binding.webView.loadUrl(state.disclaimer.getUri().toString()) } private fun updateUiVisibility(disclaimer: Disclaimer, progressState: ProgressState?) = with(binding) { @@ -111,16 +115,19 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs } ProgressState.Error -> { root.beginDelayedTransition() - webView.hide() - groupAccept.hide() + webView.show() + groupAccept.show() groupLoading.hide() - groupError.show() + groupError.hide() + webView.loadLocalTermsOfServices() + } + else -> { + webView.loadUrl(disclaimer.getUri().toString()) } - else -> {} } } - override fun handleOnBackPressed() { - store.dispatch(DisclaimerAction.OnBackPressed) + private fun WebView.loadLocalTermsOfServices() { + loadData(localTermsOfServices, "text/html", "UTF-8") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt new file mode 100644 index 0000000000..6651d0fa75 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt @@ -0,0 +1,66 @@ +package com.tangem.tap.features.disclaimer.ui + +internal val localTermsOfServices = """ + + + + + + + Legal Disclaimer + + +

Legal Disclaimer

+

    1. Tangem application (Software)

+

    The Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The + Software DOES NOT:

+

    a) Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain + wallets holding digital assets, including crypto-currency.

+

    b) Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases + that can be used to restore or to copy private (secret) keys to blockchain wallets holding digital assets, including + crypto-currency.

+

    c) Provide exchange, trading, investment services on behalf of Tangem AG.

+

    2. Risks related to the use of Software

+

    Tangem will not be responsible for any losses, damages or claims arising from events falling within the + scope of the following five categories:

+

    a) Mistakes made by the user of any cryptocurrency-related software or service, e.g., forgotten + passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Cards.

+

    b) Problems of Software and/or any blockchain- or cryptocurrency- related software or service, e.g., + corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware.

+

    c) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related + software or service, e.g., data loss due to a faulty or damaged storage device.

+

    d) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., + unauthorized access to users' wallets and/or accounts.

+

    e) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy + of service providers, information security attacks on service providers, and fraud conducted by third parties.

+

    3. Trading and Investment risks

+

    There is considerable exposure to risk in any crypto-currency or other digital asset exchange + transaction. Any transaction involving currencies involves risks including, but not limited to, the potential for + changing economic conditions that may substantially affect the price or liquidity of a currency. Investments in + crypto-currency exchange speculation may also be susceptible to sharp rises and falls as the relevant market values + fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital.

+

    4. Electronic Trading Risks

+

    Before you engage in transactions using an electronic system, you should carefully review the rules and + regulations of the exchanges offering the system and/or listing the instruments you intend to trade. Online trading + has inherent risk due to system response and access times that may vary due to market conditions, system + performance, and other factors. You should understand these and additional risks before trading.

+

    5. Compliance with tax obligations        

+

    The users of the Software are solely responsible to determinate what, if any, taxes apply to their + crypto-currency transactions. The owners of, or contributors to, the Software are NOT responsible for determining + the taxes that apply to crypto-currency transactions.        

+

    6. No warranties        

+

    The Software is provided on an "as is" basis without any warranties of any kind regarding the + Software and/or any content, data, materials and/or services provided on the Software.       +  

+

    7. Limitation of liability

+

    Unless otherwise required by law, in no event shall the owners of, or contributors to, the Software be + liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data + arising out of or in any way connected with the use of the Software. In no way are the owners of, or contributors + to, the Software responsible for the actions, decisions, or other behavior taken or not taken by you in reliance + upon the Software.

+

    8. Last amendment

+

    This disclaimer was amended for the last time on October 1st, 2020.

+ +""".trimIndent() \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index b9e5db5876..6079110e39 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -62,7 +62,6 @@ object OnboardingHelper { AppScreen.OnboardingOther } ProductType.Twins -> AppScreen.OnboardingTwins - ProductType.SaltPay -> AppScreen.OnboardingWallet ProductType.Start2Coin -> throw java.lang.UnsupportedOperationException( "Onboarding for Start2Coin cards is not supported", ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt deleted file mode 100644 index ab267f9dda..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingSaltPayHelper.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.tangem.tap.features.onboarding - -import com.tangem.common.services.Result -import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.common.extensions.successOr -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.update -import com.tangem.tap.preferencesStorage - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayHelper { - - suspend fun isOnboardingCase(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result { - return try { - val updatedStep = manager.update(SaltPayActivationStep.None, null).successOr { return it } - - val cardStorage = preferencesStorage.usedCardsPrefStorage - val activationIsFinished = cardStorage.isActivationFinished(scanResponse.card.cardId) - if (updatedStep == SaltPayActivationStep.Success && !activationIsFinished) { - cardStorage.activationFinished(scanResponse.card.cardId) - } - val isActivationCase = updatedStep != SaltPayActivationStep.Success - val isBackupCase = scanResponse.card.backupStatus?.isActive == false - Result.Success(isActivationCase || isBackupCase) - } catch (ex: Exception) { - Result.Failure(ex) - } catch (error: SaltPayActivationError) { - Result.Failure(error) - } - } - - @Suppress("UnusedPrivateMember") - fun testProceedToOnboarding(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result = - Result.Success(true) - - @Suppress("UnusedPrivateMember") - fun testProceedToMainScreen(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result = - Result.Success(false) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 227cdb626d..c021c9fad5 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -10,8 +10,6 @@ import org.rekotlin.Action sealed class OnboardingWalletAction : Action { object Init : OnboardingWalletAction() object GetToCreateWalletStep : OnboardingWalletAction() - object GetToSaltPayStep : OnboardingWalletAction() - object CreateWallet : OnboardingWalletAction() data class WalletWasCreated( val result: CompletionResult, @@ -57,7 +55,7 @@ sealed class BackupAction : Action { object ScanPrimaryCard : BackupAction() /** - * Check for unfinished backup of standard Wallets and SaltPay cards + * Check for unfinished backup of standard Wallets * See more GlobalAction.Onboarding.StartForUnfinishedBackup */ object CheckForUnfinishedBackup : BackupAction() @@ -82,9 +80,6 @@ sealed class BackupAction : Action { data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction() data class WriteBackupCard(val cardNumber: Int) : BackupAction() - /** - * It always calls for the SaltPay cards when resuming the activation process. - */ data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction() object DiscardBackup : BackupAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index ce33520089..2f4be5416d 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.ifNotNull import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO @@ -19,7 +18,6 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen @@ -30,8 +28,6 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.Artwork import kotlinx.coroutines.launch @@ -70,7 +66,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: when (action) { OnboardingWalletAction.Init -> { ifNotNull(onboardingManager, card) { manager, card -> - if (!card.isSaltPay && !manager.isActivationStarted(card.cardId)) { + if (!manager.isActivationStarted(card.cardId)) { Analytics.send(Onboarding.Started()) } } @@ -83,20 +79,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: store.dispatch(OnboardingWalletAction.ResumeBackup) } card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> { - when { - // check for unfinished backup for saltPay cards. See more - scanResponse.cardTypesResolver.isSaltPay() && backupService.hasIncompletedBackup -> { - store.dispatch(OnboardingWalletAction.ResumeBackup) - } - - scanResponse.cardTypesResolver.isSaltPay() -> { - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - store.dispatch(BackupAction.FinishBackup(withAnalytics = false)) - store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) - } - - else -> store.dispatch(BackupAction.FinishBackup()) - } + store.dispatch(BackupAction.FinishBackup()) } else -> { store.dispatch(OnboardingWalletAction.GetToCreateWalletStep) @@ -180,12 +163,8 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: if (onboardingWalletState.backupState.backupStep == BackupStep.InitBackup || onboardingWalletState.backupState.backupStep == BackupStep.Finished ) { - if (onboardingWalletState.isSaltPay) { - BackupAction.StartBackup - } else { - Analytics.send(Onboarding.Backup.ScreenOpened()) - BackupAction.IntroduceBackup - } + Analytics.send(Onboarding.Backup.ScreenOpened()) + BackupAction.IntroduceBackup } else { null } @@ -348,11 +327,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } is BackupAction.AddBackupCard -> { - onboardingWalletState.backupCardIdFilter?.let { - backupService.skipCompatibilityChecks = true - tangemSdk.config.filter.cardIdFilter = it - } - backupService.addBackupCard { result -> backupService.skipCompatibilityChecks = false tangemSdk.config.filter.cardIdFilter = null @@ -400,7 +374,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (action.accessCodeConfirmation == backupState.accessCode) { store.dispatch(BackupAction.SetAccessCodeError(null)) backupService.setAccessCode(action.accessCodeConfirmation) - store.dispatch(OnboardingSaltPayAction.SetAccessCode(action.accessCodeConfirmation)) store.dispatch(BackupAction.PrepareToWritePrimaryCard) } else { store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodesDoNotMatch)) @@ -421,7 +394,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (result) { is CompletionResult.Success -> { if (backupService.currentState == BackupService.State.Finished) { - initSaltPayOnBackupFinishedIfNeeded(scanResponse, onboardingWalletState) store.dispatchOnMain(BackupAction.FinishBackup()) } else { store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1)) @@ -450,14 +422,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.dispatch( GlobalAction.Onboarding.StartForUnfinishedBackup( addedBackupCardsCount = backupService.addedBackupCardsCount, - isSaltPayVisa = backupService.primaryCardIsSaltPayVisa(), ), ) store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } is BackupAction.SkipBackup -> { Analytics.send(Onboarding.Backup.Skipped()) - if (onboardingWalletState.isSaltPay) return Analytics.send(Onboarding.Finished()) finishCardActivation(gatherCardIds(backupState, card)) @@ -466,7 +436,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) if (action.withAnalytics) { Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) } - if (onboardingWalletState.isSaltPay) return val notActivatedCardIds = gatherCardIds(backupState, card) .mapNotNull { if (cardActivationIsFinished(it)) null else it } @@ -492,7 +461,6 @@ private fun cardActivationIsFinished(cardId: String): Boolean { /** * Standard Wallet cards start activation at OnboardingWalletAction.CreateWallet - * SaltPay cards start activation at OnboardingWalletAction.StartSaltPayCardActivation */ private fun startCardActivation(scanResponse: ScanResponse) { preferencesStorage.usedCardsPrefStorage.activationStarted(scanResponse.card.cardId) @@ -500,7 +468,6 @@ private fun startCardActivation(scanResponse: ScanResponse) { /** * Standard Wallet cards finish activation at BackupAction.SkipBackup and BackupAction.FinishBackup - * SaltPay cards finish activation at OnboardingWalletAction.FinishOnboarding */ internal fun finishCardActivation(cardIds: List) { cardIds.forEach { cardId -> @@ -514,31 +481,10 @@ internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List { - if (state.isSaltPay) { - showInterruptOnboardingDialog() - } else { - OnboardingHelper.onInterrupted() - store.dispatch(NavigationAction.PopBackTo()) - } + OnboardingHelper.onInterrupted() + store.dispatch(NavigationAction.PopBackTo()) } } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 30b94a73c6..077e4b82aa 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -1,11 +1,8 @@ package com.tangem.tap.features.onboarding.products.wallet.redux -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.tap.backupService import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayReducer import org.rekotlin.Action object OnboardingWalletReducer { @@ -17,15 +14,11 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet return when (action) { is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state) - is OnboardingSaltPayAction -> OnboardingSaltPayReducer.reduce(action, state) - is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState, state.isSaltPay)) + is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState)) is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state) is OnboardingWalletAction.GetToCreateWalletStep -> state.copy( step = OnboardingWalletStep.CreateWallet, ) - is OnboardingWalletAction.GetToSaltPayStep -> state.copy( - step = OnboardingWalletStep.SaltPay, - ) is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup) is OnboardingWalletAction.SetArtworkUrl -> { state.copy(cardArtworkUri = action.artworkUri) @@ -36,16 +29,17 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet } private object ReducerForGlobalAction { + + private const val MAX_BACKUP_CARDS = 2 + fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState { return when (action) { is GlobalAction.Onboarding.Start -> { - val isSaltPay = action.scanResponse.cardTypesResolver.isSaltPay() OnboardingWalletState( backupState = state.backupState.copy( - maxBackupCards = maxBackupCards(isSaltPay), - canSkipBackup = if (isSaltPay) false else action.canSkipBackup, + maxBackupCards = MAX_BACKUP_CARDS, + canSkipBackup = action.canSkipBackup, ), - isSaltPay = isSaltPay, ) } is GlobalAction.Onboarding.StartForUnfinishedBackup -> { @@ -55,14 +49,11 @@ private object ReducerForGlobalAction { canSkipBackup = false, isInterruptedBackup = true, ), - isSaltPay = action.isSaltPayVisa, ) } else -> state } } - - private fun maxBackupCards(isSaltPay: Boolean): Int = if (isSaltPay) 1 else 2 } private object OnboardingWallet2Reducer { @@ -78,11 +69,11 @@ private object OnboardingWallet2Reducer { private object BackupReducer { @Suppress("ComplexMethod") - fun reduce(action: BackupAction, state: BackupState, isSaltPay: Boolean): BackupState { + fun reduce(action: BackupAction, state: BackupState): BackupState { return when (action) { is BackupAction.IntroduceBackup -> BackupState( backupStep = BackupStep.InitBackup, - maxBackupCards = if (isSaltPay) 1 else 2, + maxBackupCards = 2, canSkipBackup = state.canSkipBackup, ) BackupAction.StartAddingPrimaryCard -> state.copy(backupStep = BackupStep.ScanOriginCard) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 94ce0d1f54..cc59a4fb61 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -2,11 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap import android.net.Uri -import com.tangem.common.CardFilter -import com.tangem.domain.common.SaltPayWorkaround import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep import org.rekotlin.StateType /** @@ -16,32 +12,21 @@ data class OnboardingWalletState( val step: OnboardingWalletStep = OnboardingWalletStep.None, val wallet2State: OnboardingWallet2State? = null, val backupState: BackupState = BackupState(), - val onboardingSaltPayState: OnboardingSaltPayState? = null, - val isSaltPay: Boolean = false, val cardArtworkUri: Uri? = null, val showConfetti: Boolean = false, ) : StateType { - val backupCardIdFilter: CardFilter.Companion.CardIdFilter? - get() = when { - isSaltPay -> CardFilter.Companion.CardIdFilter.Allow( - items = SaltPayWorkaround.walletCardIds.toSet(), - ranges = SaltPayWorkaround.walletCardIdRanges, - ) - else -> null - } - @Suppress("MagicNumber") fun getMaxProgress(): Int { - val baseProgress = if (isSaltPay) 12 else 6 + val baseProgress = 6 return getWallet2Progress() + baseProgress } @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { - val progressByStep = when { - step == OnboardingWalletStep.CreateWallet -> 1 - step == OnboardingWalletStep.Backup -> { + val progressByStep = when (step) { + OnboardingWalletStep.CreateWallet -> 1 + OnboardingWalletStep.Backup -> { when (backupState.backupStep) { BackupStep.InitBackup -> 2 BackupStep.ScanOriginCard -> 3 @@ -53,21 +38,7 @@ data class OnboardingWalletState( BackupStep.Finished -> getMaxProgress() } } - step == OnboardingWalletStep.SaltPay && isSaltPay -> { - when (onboardingSaltPayState!!.step) { - SaltPayActivationStep.None, SaltPayActivationStep.NoGas -> 0 - SaltPayActivationStep.NeedPin -> 7 - SaltPayActivationStep.CardRegistration -> 8 - SaltPayActivationStep.KycIntro, SaltPayActivationStep.KycStart, - SaltPayActivationStep.KycWaiting, SaltPayActivationStep.KycReject, - -> 9 - SaltPayActivationStep.Claim -> 10 - SaltPayActivationStep.ClaimInProgress -> 11 - SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success, - -> getMaxProgress() - } - } - step == OnboardingWalletStep.Done -> getMaxProgress() + OnboardingWalletStep.Done -> getMaxProgress() else -> 1 } @@ -82,7 +53,7 @@ data class OnboardingWallet2State( ) enum class OnboardingWalletStep { - None, CreateWallet, SaltPay, Backup, Done + None, CreateWallet, Backup, Done } data class BackupState( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt deleted file mode 100644 index fa982df1c9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ /dev/null @@ -1,236 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.blockchains.ethereum.CompiledEthereumTransaction -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionSigner -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.blockchain.extensions.Result -import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.extensions.guard -import com.tangem.domain.common.extensions.successOr -import com.tangem.tap.common.extensions.isPositive -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.stub -import com.tangem.tap.domain.getFirstToken -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode -import java.util.concurrent.atomic.AtomicLong - -/** -[REDACTED_AUTHOR] - */ -class GnosisRegistrator( - val walletManager: EthereumWalletManager, -) { - - private val walletAddress = walletManager.wallet.address - private val blockchain = walletManager.wallet.blockchain - private val token = walletManager.wallet.getFirstToken().guard { - throw NullPointerException("WalletManager for GnosisRegistrator must contain token") - } - - private val otpProcessorContractAddress: String = when (blockchain) { - Blockchain.SaltPay -> "0xc659f4FEd7A84a188F54cBA4A7a49D77c1a20522" - else -> throw IllegalArgumentException("GnosisRegistrator supports only the SaltPay blockchain") - } - - private val addressTreasureSafe = "0x24A3c2382497075b6D93258f5938f7B661c06318" - - private val atomicNonce = AtomicLong() - - suspend fun checkHasGas(): Result { - // return Result.Success(true) // test - val wallet = walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - - val hasGas = wallet.amounts[AmountType.Coin]?.value?.isPositive() ?: false - return Result.Success(hasGas) - } - - suspend fun prepareBeforeMakingTxs(): Result { - walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - atomicNonce.set(walletManager.txCount) - - return Result.Success(Unit) - } - - suspend fun sendTransactions(transactions: List): Result> { - val results = transactions.map { - coroutineScope { async { walletManager.sendRaw(it.compiledTransaction, it.signature) } } - }.awaitAll() - - val errors = results.filterIsInstance() - return if (errors.isEmpty()) { - Result.Success(listOf()) - } else { - Result.Failure(errors[0].error) - } - } - - suspend fun makeSetSpendLimitTx(value: BigDecimal): Result { - val limitAmount = Amount(walletManager.cardTokens.first(), value) - val feeAmount = walletManager.getFeeToSetSpendLimit(otpProcessorContractAddress, limitAmount) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildSetSpendLimitToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - amount = limitAmount, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToSetSpendLimit, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'setSpendLimit' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeInitOtpTx(rootOTP: ByteArray, rootOTPCounter: Int): Result { - val feeAmount = walletManager.getFeeToInitOTP(otpProcessorContractAddress, rootOTP, rootOTPCounter) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildInitOTPToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - otp = rootOTP, - otpCounter = rootOTPCounter, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToInitOTP, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'initOtp' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeSetWalletTx(): Result { - val feeAmount = walletManager.getFeeToSetWallet(otpProcessorContractAddress) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildSetWalletToSign( - processorContractAddress = otpProcessorContractAddress, - cardAddress = walletAddress, - transactionFee = feeAmount, - gasLimit = walletManager.gasLimitToSetWallet, - nonce = atomicNonce.getAndIncrement().toBigInteger(), - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'setWallet' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun makeApprovalTx(value: BigDecimal): Result { - val approveAmount = Amount(token, value) - val feeAmount = walletManager.getFeeToApprove(approveAmount, otpProcessorContractAddress) - .extractFeeAmount().successOr { return it } - - val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign( - transactionData = walletManager.createTransaction( - amount = approveAmount, - fee = Fee.Common(feeAmount), - destination = otpProcessorContractAddress, - ), - nonce = atomicNonce.getAndIncrement().toBigInteger(), - gasLimit = walletManager.gasLimitToApprove, - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Can't create the 'approval' transaction")) - - return Result.Success(compiledEthereumTransaction) - } - - suspend fun getAllowance(): Result { - // return Result.Success(Amount(BigDecimal(0.1), walletManager.wallet.blockchain)) // test - return walletManager.getAllowance(addressTreasureSafe, token) - } - - suspend fun transferFrom(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - if (walletManager.txCount == -1L) { - walletManager.safeUpdate().successOr { - return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error)) - } - } - - // return transferFromStandardWay(amountToClaim, signer) - return transferFromHardcodeWay(amountToClaim, signer) - } - - @Suppress("UnusedPrivateMember") - private suspend fun transferFromStandardWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - val amount = Amount(token, amountToClaim) - val feeAmount = walletManager.getFeeToTransferFrom(amount, addressTreasureSafe) - .extractFeeAmount().successOr { return it } - - val transactionData = walletManager.createTransferFromTransaction(amount, feeAmount, addressTreasureSafe) - walletManager.transferFrom(transactionData, signer).successOr { return Result.Failure(it.error) } - - return Result.Success(Unit) - } - - private suspend fun transferFromHardcodeWay(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - val amount = Amount(token, amountToClaim) - val gasPrice = walletManager.getGasPrice().successOr { return it } - - val hardcodeGasLimit = BigInteger("300000") - val hardcodeFeeValue = (hardcodeGasLimit * gasPrice).toBigDecimal( - scale = blockchain.decimals(), - mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), - ).movePointLeft(blockchain.decimals()) - val hardcodeFeeAmount = Amount(hardcodeFeeValue, blockchain) - - val transactionData = walletManager.createTransferFromTransaction( - amount = amount, - feeAmount = hardcodeFeeAmount, - source = addressTreasureSafe, - ) - val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign( - transactionData, - walletManager.txCount.toBigInteger(), - hardcodeGasLimit, - ) ?: return Result.Failure(BlockchainSdkError.CustomError("Not enough data")) - - return when (val result = walletManager.signAndSend(transactionToSign, signer)) { - SimpleResult.Success -> Result.Success(Unit) - is SimpleResult.Failure -> Result.Failure(result.error) - } - } - - @Suppress("MagicNumber") - private fun Result.extractFeeAmount(): Result { - return when (this) { - is Result.Success -> { - when(data) { - is TransactionFee.Single -> { - Result.Failure(BlockchainSdkError.FailedToLoadFee) - } - is TransactionFee.Choosable -> { - val normalFee = (data as TransactionFee.Choosable).normal - Result.Success(normalFee.amount) - } - } - } - is Result.Failure -> this - } - } - - private fun Long?.toBigInteger(): BigInteger? = this?.let { BigInteger.valueOf(it) } - - private fun Long.toBigInteger(): BigInteger = BigInteger.valueOf(this) - - companion object { - fun stub(): GnosisRegistrator { - return GnosisRegistrator(WalletManager.stub()) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt deleted file mode 100644 index fd359c25a4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/PinFilters.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.common.Filter - -/** -[REDACTED_AUTHOR] - */ -interface PinFilter : Filter - -class AllSymbolsTheSameFilter : PinFilter { - override fun filter(value: String): Boolean { - if (value.isEmpty()) return false - - val array = value.toCharArray() - val firstSymbol = array[0] - return array.all { it == firstSymbol } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt deleted file mode 100644 index 2f7f824f6c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayActivationManager.kt +++ /dev/null @@ -1,341 +0,0 @@ -@file:Suppress("MaximumLineLength") - -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import android.net.Uri -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.TransactionSigner -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.isZero -import com.tangem.common.extensions.toHexString -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.datasource.api.paymentology.PaymentologyApiService -import com.tangem.datasource.api.paymentology.models.request.CheckRegistrationRequests -import com.tangem.datasource.api.paymentology.models.request.RegisterKYCRequest -import com.tangem.datasource.api.paymentology.models.request.RegisterWalletRequest -import com.tangem.datasource.api.paymentology.models.response.AttestationResponse -import com.tangem.datasource.api.paymentology.models.response.RegisterWalletResponse -import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse -import com.tangem.datasource.api.paymentology.models.response.tryExtractError -import com.tangem.datasource.config.models.KYCProvider -import com.tangem.datasource.config.models.SaltPayConfig -import com.tangem.domain.models.scan.CardDTO -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.domain.common.extensions.successOr -import com.tangem.operations.attestation.AttestWalletKeyResponse -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.domain.getFirstToken -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.store -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.math.BigDecimal -import kotlin.ByteArray -import kotlin.Exception -import kotlin.IllegalStateException -import kotlin.NullPointerException -import kotlin.String -import kotlin.Suppress -import kotlin.Unit -import kotlin.UnsupportedOperationException -import kotlin.byteArrayOf -import kotlin.minus -import com.tangem.blockchain.extensions.Result as BlockchainResult - -/** -[REDACTED_AUTHOR] - */ -class SaltPayActivationManager( - val cardId: String, - val cardPublicKey: ByteArray, - private val kycProvider: KYCProvider, - private val paymentologyService: PaymentologyApiService, - private val gnosisRegistrator: GnosisRegistrator, -) { - - val walletPublicKey = gnosisRegistrator.walletManager.wallet.publicKey.seedKey - val kycUrlProvider = KYCUrlProvider(walletPublicKey, kycProvider) - - private val approvalValue: BigDecimal = BigDecimal(2).pow(256).minus(BigDecimal.ONE) - .movePointLeft(gnosisRegistrator.walletManager.wallet.blockchain.decimals()) - - private val spendLimitValue: BigDecimal = BigDecimal("100") - - suspend fun checkHasGas(): Result { - return when (val hasGasResult = gnosisRegistrator.checkHasGas()) { - is BlockchainResult.Success -> if (hasGasResult.data) { - Result.Success(Unit) - } else { - Result.Failure(SaltPayActivationError.NoGas) - } - is BlockchainResult.Failure -> Result.Failure(hasGasResult.error as BlockchainSdkError) - } - } - - suspend fun registerKYC(): Result { - val result = withContext(Dispatchers.IO) { - performRequest { paymentologyService.api.registerKYC(makeRegisterKYCRequest()) } - } - return when (result) { - is Result.Success -> Result.Success(Unit) - is Result.Failure -> result - } - } - - suspend fun checkRegistration(): Result { - val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) - val request = CheckRegistrationRequests(listOf(requestItem)) - - val response: RegistrationResponse = withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.checkRegistration(request) - } - } - .successOr { return it } - .tryExtractError() - .successOr { return it } - - return try { - if (response.results.isEmpty()) throw SaltPayActivationError.EmptyResponse - - val item = response.results[0] - if (item.error != null) throw IllegalStateException(response.makeErrorMessage()) - - Result.Success(item) - } catch (ex: Exception) { - Result.Failure(ex) - } - } - - suspend fun requestAttestationChallenge(): Result { - val requestItem = CheckRegistrationRequests.Item(cardId, cardPublicKey.toHexString()) - return withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.requestAttestationChallenge(requestItem) - } - } - .successOr { return it } - .tryExtractError() - } - - suspend fun sendTransactions(signedTransactions: List): BlockchainResult> { - return gnosisRegistrator.sendTransactions(signedTransactions) - } - - suspend fun registerWallet( - attestResponse: AttestWalletKeyResponse, - pinCode: String, - ): Result { - val request = RegisterWalletRequest( - cardId = cardId, - publicKey = cardPublicKey, - walletPublicKey = walletPublicKey, - walletSalt = attestResponse.salt, - walletSignature = attestResponse.walletSignature, - cardSalt = attestResponse.publicKeySalt ?: byteArrayOf(), - cardSignature = attestResponse.cardSignature ?: byteArrayOf(), - pin = pinCode, - ) - return withContext(Dispatchers.IO) { - performRequest { - paymentologyService.api.registerWallet(request) - } - } - .successOr { return it } - .tryExtractError() - } - - suspend fun getAmountToClaim(): Result { - val amount = gnosisRegistrator.getAllowance().successOr { - return Result.Failure(SaltPayActivationError.FailedToGetFundsToClaim) - } - - return if (amount.value == null || amount.value?.isZero() == true) { - Result.Failure(SaltPayActivationError.NoFundsToClaim) - } else { - Result.Success(amount) - } - } - - suspend fun claim(amountToClaim: BigDecimal, signer: TransactionSigner): Result { - gnosisRegistrator.transferFrom(amountToClaim, signer).successOr { - val userCancelledError = (it.error as? BlockchainSdkError.WrappedTangemError) - ?.tangemError as? TangemSdkError.UserCancelled - - return when (userCancelledError) { - null -> Result.Failure(SaltPayActivationError.ClaimTransactionFailed) - else -> Result.Failure(userCancelledError) - } - } - return Result.Success(Unit) - } - - suspend fun getTokenAmount(): Result { - val wallet = gnosisRegistrator.walletManager.safeUpdate().successOr { return it } - val token = wallet.getFirstToken().guard { - throw UnsupportedOperationException() - } - - val amountValue = wallet.amounts[AmountType.Token(token)]?.value.guard { - throw UnsupportedOperationException() - } - - return Result.Success(amountValue) - } - - fun makeRegistrationTask(challenge: ByteArray): SaltPayRegistrationTask { - return SaltPayRegistrationTask( - gnosisRegistrator = gnosisRegistrator, - challenge = challenge, - walletPublicKey = walletPublicKey, - approvalValue = approvalValue, - spendLimitValue = spendLimitValue, - ) - } - - private fun makeRegisterKYCRequest(): RegisterKYCRequest = RegisterKYCRequest( - cardId = cardId, - publicKey = cardPublicKey, - kycProvider = "UTORG", - kycRefId = kycUrlProvider.kycRefId, - ) - - companion object { - fun stub(): SaltPayActivationManager = SaltPayActivationManager( - kycProvider = SaltPayConfig.stub().kycProvider, - paymentologyService = PaymentologyApiService, - gnosisRegistrator = GnosisRegistrator.stub(), - cardId = "", - cardPublicKey = byteArrayOf(), - ) - } -} - -class KYCUrlProvider( - walletPublicKey: ByteArray, - kycProvider: KYCProvider, -) { - - val kycRefId = UserWalletIdBuilder.walletPublicKey(walletPublicKey).stringValue - - val requestUrl: String = makeWebRequestUrl(kycProvider) - - val doneUrl = "https://success.tangem.com/" - - private fun makeWebRequestUrl(kycProvider: KYCProvider): String { - val baseUri = Uri.parse(kycProvider.baseUrl) - return Uri.Builder() - .scheme(baseUri.scheme) - .authority(baseUri.authority) - .encodedPath(baseUri.path) - .appendQueryParameter(kycProvider.sidParameterKey, kycProvider.sidValue) - .appendQueryParameter(kycProvider.externalIdParameterKey, kycRefId) - .build().toString() - } -} - -class SaltPayActivationManagerFactory( - private val blockchain: Blockchain, - private val card: CardDTO, -) { - - fun create(): SaltPayActivationManager { - val wallet = card.wallets.first() - val saltPayConfig = store.state.globalState.configManager?.config?.saltPayConfig.guard { - throw NullPointerException("SaltPayConfig is not initialized") - } - val wmFactory = store.state.globalState.tapWalletManager.walletManagerFactory - val paymentologyService = store.state.domainNetworks.paymentologyService - - // return createDummyActivationManager(saltPayConfig, wmFactory, paymentologyService) - return createActivationPayManager( - cardId = card.cardId, - cardPublicKey = card.cardPublicKey, - kycProvider = saltPayConfig.kycProvider, - gnosisRegistrator = GnosisRegistrator( - walletManager = makeSaltPayWalletManager( - blockchain = blockchain, - walletPublicKey = wallet.publicKey, - wmFactory = wmFactory, - ), - ), - paymentologyService = paymentologyService, - ) - } - - private fun makeSaltPayWalletManager( - blockchain: Blockchain, - walletPublicKey: ByteArray, - wmFactory: WalletManagerFactory, - ): EthereumWalletManager { - return wmFactory.makeWalletManager( - blockchain = blockchain, - publicKey = Wallet.PublicKey(walletPublicKey, null, null), - tokens = listOf(SaltPayWorkaround.tokenFrom(blockchain)), - ) as EthereumWalletManager - } - - private fun createActivationPayManager( - cardId: String, - cardPublicKey: ByteArray, - kycProvider: KYCProvider, - gnosisRegistrator: GnosisRegistrator, - paymentologyService: PaymentologyApiService, - ): SaltPayActivationManager { - return SaltPayActivationManager( - cardId = cardId, - cardPublicKey = cardPublicKey, - kycProvider = kycProvider, - paymentologyService = paymentologyService, - gnosisRegistrator = gnosisRegistrator, - ) - } - - @Suppress("UnusedPrivateMember") - private fun createDummyActivationManager( - saltPayConfig: SaltPayConfig, - wmFactory: WalletManagerFactory, - paymentologyService: PaymentologyApiService, - ): SaltPayActivationManager { - val fakeSource = DummySaltPayCardSource() - - val walletManager = wmFactory.makeWalletManager( - blockchain = fakeSource.blockchain, - publicKey = fakeSource.blockchainPublicKey, - tokens = listOf(fakeSource.token), - ) as EthereumWalletManager - - return createActivationPayManager( - cardId = fakeSource.cardId, - cardPublicKey = fakeSource.cardPublicKey, - kycProvider = saltPayConfig.kycProvider, - gnosisRegistrator = GnosisRegistrator(walletManager), - paymentologyService = paymentologyService, - ) - } - - private data class DummySaltPayCardSource( - val blockchain: Blockchain = Blockchain.SaltPay, - val cardId: String = "FF03000001001057", - private val cardPublicKeyString: String = "039C2D2F9B68003766BFC29766761F55F4084E48B8B012BF439E115A79AB122D48", - private val walletPublicKeyString: String = "0399B2DF5B129FBF69097DAA7C44FB5849E578CCFC22228A409870C74B56C4C3D1", - ) { - val cardPublicKey: ByteArray = cardPublicKeyString.hexToBytes() - val walletPublicKey: ByteArray = walletPublicKeyString.hexToBytes() - val blockchainPublicKey = Wallet.PublicKey(walletPublicKey, null, null) - val token: Token = SaltPayWorkaround.tokenFrom(blockchain) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt deleted file mode 100644 index 8d116a7385..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayExceptionHandler.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.common.core.TangemSdkError -import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.store - -/** -[REDACTED_AUTHOR] - */ -object SaltPayExceptionHandler { - - fun handle(throwable: Throwable) { - when (throwable) { - is SaltPayActivationError -> { - val dialog = when (throwable) { - is SaltPayActivationError.NoGas -> { - Analytics.send(Onboarding.NotEnoughGasError()) - SaltPayDialog.Activation.NoGas - } - is SaltPayActivationError.PutVisaCard -> SaltPayDialog.Activation.PutVisaCard - is SaltPayActivationError.CardNotPassed -> { - Analytics.send(Onboarding.CardNotPassedError()) - SaltPayDialog.Activation.OnError(throwable) - } - else -> SaltPayDialog.Activation.OnError(throwable) - } - store.dispatchDialogShow(dialog) - } - is TangemSdkError -> { - when (throwable) { - is TangemSdkError.NetworkError -> { - val message = throwable.customMessage - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - else -> Unit - } - } - is BlockchainSdkError -> { - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(throwable.customMessage)) - } - else -> { - val message = throwable.localizedMessage ?: "SaltPay unknown error" - store.dispatchDialogShow(AppDialog.SimpleOkErrorDialog(message)) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt deleted file mode 100644 index fb4bd50ed9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/SaltPayRegistrationTask.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import com.tangem.blockchain.blockchains.ethereum.CompiledEthereumTransaction -import com.tangem.blockchain.blockchains.ethereum.SignedEthereumTransaction -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.CompletionResult -import com.tangem.common.core.CardSession -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.CompletionCallback -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.operations.CommandResponse -import com.tangem.operations.GenerateOTPCommand -import com.tangem.operations.GenerateOTPResponse -import com.tangem.operations.attestation.AttestWalletKeyCommand -import com.tangem.operations.attestation.AttestWalletKeyResponse -import com.tangem.operations.sign.SignHashesCommand -import com.tangem.tap.scope -import kotlinx.coroutines.launch -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class RegistrationTaskResponse( - val signedTransactions: List, - val attestResponse: AttestWalletKeyResponse, -) : CommandResponse - -class SaltPayRegistrationTask( - private var gnosisRegistrator: GnosisRegistrator, - private var challenge: ByteArray, - private val walletPublicKey: ByteArray, - private val approvalValue: BigDecimal, - private val spendLimitValue: BigDecimal, -) : CardSessionRunnable { - - private var generateOTPResponse: GenerateOTPResponse? = null - private var attestWalletResponse: AttestWalletKeyResponse? = null - private var signedTransactions: List = listOf() - - override fun run(session: CardSession, callback: CompletionCallback) { - generateOTP(session, callback) - } - - private fun generateOTP(session: CardSession, callback: CompletionCallback) { - GenerateOTPCommand().run(session) { result -> - when (result) { - is CompletionResult.Success -> { - generateOTPResponse = result.data - attestWallet(session, callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private fun attestWallet(session: CardSession, callback: CompletionCallback) { - val card = session.environment.card.guard { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return - } - - val walletPublicKey = card.wallets.firstOrNull()?.publicKey - if (walletPublicKey == null || !walletPublicKey.contentEquals(this.walletPublicKey)) { - callback(CompletionResult.Failure(TangemSdkError.WalletNotFound())) - return - } - - val attestWalletCommand = AttestWalletKeyCommand( - publicKey = walletPublicKey, - challenge = challenge, - confirmationMode = AttestWalletKeyCommand.ConfirmationMode.Dynamic, - ) - attestWalletCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - attestWalletResponse = result.data - scope.launch { prepareTransactions(session, callback) } - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private suspend fun prepareTransactions( - session: CardSession, - callback: CompletionCallback, - ) { - val generateOTPResponse = this.generateOTPResponse.guard { - callback(CompletionResult.Failure(TangemSdkError.UnknownError())) - return - } - - gnosisRegistrator.prepareBeforeMakingTxs().successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - val approvalTx = gnosisRegistrator.makeApprovalTx(approvalValue).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - val setWalletTx = gnosisRegistrator.makeSetWalletTx().successOr { - callback(CompletionResult.Failure(it.error)) - return - } - val initOtpTx = gnosisRegistrator.makeInitOtpTx( - generateOTPResponse.rootOTP, - generateOTPResponse.rootOTPCounter, - ).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - val setSpendLimitTx = gnosisRegistrator.makeSetSpendLimitTx(spendLimitValue).successOr { - callback(CompletionResult.Failure(it.error)) - return - } - - signTransactions( - transactions = listOf( - approvalTx, - setWalletTx, - initOtpTx, - setSpendLimitTx, - ), - session = session, - callback = callback, - ) - } - - private fun signTransactions( - transactions: List, - session: CardSession, - callback: CompletionCallback, - ) { - val walletPublicKey = session.environment.card?.wallets?.firstOrNull()?.publicKey.guard { - callback(CompletionResult.Failure(TangemSdkError.WalletNotFound())) - return - } - - val hashes = transactions.map { it.hash }.toTypedArray() - val signHashesCommand = SignHashesCommand(hashes, walletPublicKey) - - signHashesCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - signedTransactions = transactions.zip(result.data.signatures).map { - SignedEthereumTransaction(it.first, it.second) - } - complete(callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - - private fun complete(callback: CompletionCallback) { - if (signedTransactions.isEmpty() || attestWalletResponse == null) { - callback(CompletionResult.Failure(TangemSdkError.UnknownError())) - } else { - val result = RegistrationTaskResponse(signedTransactions, attestWalletResponse!!) - callback(CompletionResult.Success(result)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt deleted file mode 100644 index 2a33de86f4..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/UtorgWebView.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay - -import android.app.Activity -import android.content.Intent -import android.net.Uri -import android.provider.MediaStore -import android.text.TextUtils -import android.webkit.PermissionRequest -import android.webkit.ValueCallback -import android.webkit.WebChromeClient -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.activity.ComponentActivity -import androidx.core.app.ActivityCompat -import com.tangem.tap.common.ActivityResultCallbackHolder -import com.tangem.tap.common.OnActivityResultCallback -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.isPermissionGranted -import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.store -import com.tangem.wallet.R -import java.io.File - -/** -[REDACTED_AUTHOR] - */ -class UtorgWebViewClient( - private val onSuccess: () -> Unit, - private val successUrl: String, -) : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = - if (successUrl == request.url.toString()) { - onSuccess() - true - } else { - false - } -} - -class UtorgWebChromeClient( - private val activity: ComponentActivity, - private val activityResultCallbackHolder: ActivityResultCallbackHolder = activity as ActivityResultCallbackHolder, -) : WebChromeClient() { - - private val fileChooseRequestCode = 1002 - private val permissionRequestCode = 1003 - private val onActivityResultCallback: OnActivityResultCallback = ::onActivityResult - - private val permissionList = listOf( - "android.permission.CAMERA", - ) - - private var filePathCallback: ValueCallback>? = null - private var mediaUri: Uri? = null - - override fun onShowFileChooser( - webView: WebView?, - filePathCallback: ValueCallback>, - fileChooserParams: FileChooserParams?, - ): Boolean { - activityResultCallbackHolder.addOnActivityResultCallback(onActivityResultCallback) - if (!readyToShowFileChooser()) return false - - val type: String - if (fileChooserParams != null && fileChooserParams.acceptTypes != null && - fileChooserParams.acceptTypes.isNotEmpty() - ) { - type = if (!TextUtils.isEmpty(fileChooserParams.acceptTypes[0])) fileChooserParams.acceptTypes[0] else "*/*" - this.filePathCallback = filePathCallback - } else { - return false - } - proceedOnType(type, fileChooserParams.isCaptureEnabled) - - return true - } - - private fun readyToShowFileChooser(): Boolean { - val permissionsToGrant = permissionList.filterNot { activity.isPermissionGranted(it) }.toTypedArray() - return if (permissionsToGrant.isNotEmpty()) { - ActivityCompat.requestPermissions(activity, permissionsToGrant, permissionRequestCode) - false - } else { - true - } - } - - private fun proceedOnType(type: String, isCaptureEnabled: Boolean) { - if (isCaptureEnabled) { - mediaUri = Uri.fromFile(File(getImageCaptureCachePath())) - val imageCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - imageCaptureIntent.putExtra("output", mediaUri) - activity.startActivityForResult( - Intent.createChooser(imageCaptureIntent, "Image Chooser"), - fileChooseRequestCode, - ) - } else { - mediaUri = Uri.fromFile(File(getImageCaptureCachePath())) - - val getContentIntent = Intent(Intent.ACTION_GET_CONTENT) - getContentIntent.addCategory(Intent.CATEGORY_OPENABLE) - getContentIntent.type = "image/*" - - // val imageCaptureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - // imageCaptureIntent.putExtra("output", mediaUri) - - val chooserIntent = Intent.createChooser(getContentIntent, "Pick a photo") - // chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(imageCaptureIntent)) - - activity.startActivityForResult( - Intent.createChooser(chooserIntent, "Image Chooser"), - fileChooseRequestCode, - ) - } - } - - private fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - fileChooseRequestCode -> { - if (resultCode != Activity.RESULT_OK) { - filePathCallback?.onReceiveValue(null) - clean() - return - } - if (data == null) { - filePathCallback?.onReceiveValue(arrayOf(mediaUri!!)) - } else { - val uri = FileChooserParams.parseResult(resultCode, data) - filePathCallback?.onReceiveValue(uri) - } - clean() - } - permissionRequestCode -> { - val notGranted = permissionList.filterNot { activity.isPermissionGranted(it) } - if (notGranted.isNotEmpty()) { - val permissionsName = notGranted.joinToString("\n") { it.permissionName() } - store.dispatchDialogShow( - AppDialog.SimpleOkDialog( - header = activity.getString(R.string.common_warning), - message = "Permission access is required to pass authorization." + - "To grant permission \n" + - "$permissionsName \n" + - "you must enable them in the app's settings", - ), - ) - } - } - } - } - - override fun onPermissionRequest(request: PermissionRequest) { - request.grant(request.resources) - } - - private fun getImageCaptureCachePath(): String = "cached_photo_" + System.currentTimeMillis() + ".jpg" - - private fun clean() { - activityResultCallbackHolder.removeOnActivityResultCallback(onActivityResultCallback) - filePathCallback = null - mediaUri = null - } - - private fun String.permissionName(): String { - return this.slice(this.lastIndexOf(".") until this.length) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt deleted file mode 100644 index 5121db94db..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/NoFundsForActivationDialog.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.common.extensions.guard -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object NoFundsForActivationDialog { - - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_no_gas_title) - setMessage(R.string.saltpay_error_no_gas_message) - setPositiveButton(R.string.chat_button_title) { _, _ -> - val sprinklrConfig = store.state.globalState.configManager?.config - ?.saltPayConfig - ?.sprinklr - .guard { - store.dispatchDebugErrorNotification("SaltPayConfig not initialized") - return@setPositiveButton - } - store.dispatch(GlobalAction.OpenChat(SupportInfo(), sprinklrConfig)) - } - setNegativeButton(R.string.common_cancel) { _, _ -> - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt deleted file mode 100644 index d3daf798fe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/PutVisaCardDialog.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object PutVisaCardDialog { - fun create(context: Context): Dialog { - return AlertDialog.Builder(context).apply { - setTitle(R.string.saltpay_error_empty_backup_title) - setMessage(R.string.saltpay_error_empty_backup_message) - setPositiveButton(R.string.common_ok) { _, _ -> } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt deleted file mode 100644 index f3c184157f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/RegistrationErrorDialog.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import android.app.Dialog -import android.content.Context -import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.moduleMessage.ConvertedDialogMessage -import com.tangem.tap.domain.moduleMessage.saltPay.SaltPayErrorConverter -import com.tangem.tap.store -import com.tangem.wallet.R - -/** -[REDACTED_AUTHOR] - */ -object RegistrationErrorDialog { - fun create(context: Context, dialog: SaltPayDialog.Activation.OnError): Dialog { - val convertedMessage = SaltPayErrorConverter(context).convert(dialog.error) as ConvertedDialogMessage - - return AlertDialog.Builder(context).apply { - setTitle(convertedMessage.title) - setMessage(convertedMessage.message) - setPositiveButton(R.string.common_ok) { _, _ -> - store.dispatch(GlobalAction.HideDialog) - } - setOnDismissListener { - store.dispatch(GlobalAction.HideDialog) - } - setCancelable(false) - }.create() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt deleted file mode 100644 index 26228ba27f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/SaltPayDialog.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog - -import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError - -/** -[REDACTED_AUTHOR] - */ -sealed class SaltPayDialog : StateDialog { - sealed class Activation : SaltPayDialog() { - object NoGas : SaltPayDialog() - object PutVisaCard : SaltPayDialog() - data class OnError(val error: SaltPayActivationError) : SaltPayDialog() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt deleted file mode 100644 index c6e8d9526e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/message/SaltPayModuleMessage.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.message - -import com.tangem.common.module.ModuleError -import com.tangem.common.module.ModuleErrorCode -import com.tangem.common.module.ModuleMessage - -sealed interface SaltPayModuleMessage : ModuleMessage - -sealed class SaltPayModuleError( - subCode: Int, - override val message: String, - override val data: Any? = null, -) : SaltPayModuleMessage, ModuleError() { - override val code: Int = ModuleErrorCode.SALT_PAY + subCode - - companion object { - // base code used for all errors in the module - internal const val ERROR_CODE_REGISTRATION = 100 -// const val CODE_ANY_OTHER = 200..299, 300..399 etc - } -} - -sealed class SaltPayActivationError( - subCode: Int, - message: String? = null, - override val data: Any? = null, -) : SaltPayModuleError( - subCode = ERROR_CODE_REGISTRATION + subCode, - message = message ?: EMPTY_MESSAGE, -) { - object Unknown : SaltPayActivationError(subCode = 4) - object Empty : SaltPayActivationError(subCode = 4) - - // gnosis - object FailedToMakeTxData : SaltPayActivationError(subCode = 1) - object FailedToSendTx : SaltPayActivationError(subCode = 2) - - object NeedPin : SaltPayActivationError(subCode = 3) - object NoGas : SaltPayActivationError(subCode = 5) - object EmptyResponse : SaltPayActivationError(subCode = 6) - object CardNotFound : SaltPayActivationError(subCode = 8) - class CardDisabled(message: String?) : SaltPayActivationError(subCode = 9, message = message) - class CardNotPassed(message: String?) : SaltPayActivationError(subCode = 10, message = message) - object EmptyDynamicAttestResponse : SaltPayActivationError(subCode = 11) - object EmptyBackupCardScanned : SaltPayActivationError(subCode = 12) - object WeakPin : SaltPayActivationError(subCode = 13) - - object FailedToGetFundsToClaim : SaltPayActivationError(subCode = 14) - object NoFundsToClaim : SaltPayActivationError(subCode = 15) - object ClaimTransactionFailed : SaltPayActivationError(subCode = 16) - object PutVisaCard : SaltPayActivationError(subCode = 18) - - companion object { - const val EMPTY_MESSAGE = "" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt deleted file mode 100644 index 91b791925d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayAction.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.blockchain.common.Amount -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import org.rekotlin.Action -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -sealed class OnboardingSaltPayAction : Action { - object Init : OnboardingSaltPayAction() - - /** - * Notification about the switching from the Wallet view to the SaltPay view. - */ - object OnSwitchedToSaltPayProcess : OnboardingSaltPayAction() - - data class SetDependencies( - val registrationManager: SaltPayActivationManager, - ) : OnboardingSaltPayAction() - - data class SetInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() - data class SetClaimRefreshInProgress(val isInProgress: Boolean) : OnboardingSaltPayAction() - - data class SetAccessCode(val accessCode: String?) : OnboardingSaltPayAction() - - data class Update(val withAnalytics: Boolean = true) : OnboardingSaltPayAction() - object RegisterCard : OnboardingSaltPayAction() - object OpenUtorgKYC : OnboardingSaltPayAction() - object UtorgKYCRedirectSuccess : OnboardingSaltPayAction() - object RegisterKYC : OnboardingSaltPayAction() - object Claim : OnboardingSaltPayAction() - object RefreshClaim : OnboardingSaltPayAction() - - data class TrySetPin(val pin: String) : OnboardingSaltPayAction() - data class SetPin(val pin: String) : OnboardingSaltPayAction() - - data class SetStep( - val newStep: SaltPayActivationStep, - val withAnalytics: Boolean = true, - ) : OnboardingSaltPayAction() - - data class SetAmountToClaim(val amount: Amount?) : OnboardingSaltPayAction() - data class SetTokenBalance(val balanceValue: BigDecimal) : OnboardingSaltPayAction() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt deleted file mode 100644 index 5418a8d42e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayMiddleware.kt +++ /dev/null @@ -1,466 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.Message -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.extensions.successOr -import com.tangem.common.Filter -import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard -import com.tangem.common.services.Result -import com.tangem.core.analytics.Analytics -import com.tangem.datasource.api.paymentology.models.response.KYCStatus -import com.tangem.datasource.api.paymentology.models.response.RegistrationResponse -import com.tangem.domain.common.extensions.successOr -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.isPositive -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.TangemSigner -import com.tangem.tap.features.demo.DemoHelper -import com.tangem.tap.features.onboarding.OnboardingManager -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.products.wallet.redux.finishCardActivation -import com.tangem.tap.features.onboarding.products.wallet.redux.gatherCardIds -import com.tangem.tap.features.onboarding.products.wallet.saltPay.AllSymbolsTheSameFilter -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import com.tangem.tap.tangemSdkManager -import com.tangem.wallet.R -import kotlinx.coroutines.launch -import org.rekotlin.Action -import org.rekotlin.Middleware -import timber.log.Timber - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayMiddleware { - val handler = onboardingSaltPayMiddleware -} - -private val onboardingSaltPayMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - handleOnboardingSaltPayAction(action, state) - next(action) - } - } -} - -@Suppress("LongMethod", "ComplexMethod") -private fun handleOnboardingSaltPayAction(anyAction: Action, appState: () -> AppState?) { - if (DemoHelper.tryHandle(appState, anyAction)) return - val action = anyAction as? OnboardingSaltPayAction ?: return - appState() ?: return - - fun getAppState(): AppState = appState()!! - fun getOnboardingWalletState(): OnboardingWalletState = getAppState().onboardingWalletState - fun getOnboardingManager(): OnboardingManager? = getAppState().globalState.onboardingState.onboardingManager - fun getState(): OnboardingSaltPayState = getOnboardingWalletState().onboardingSaltPayState!! - - when (action) { - is OnboardingSaltPayAction.Init -> { - if (!getOnboardingWalletState().isSaltPay) return - val onboardingManager = getOnboardingManager().guard { - // onboardingManager may be null if it's started from standard Wallet unfinished backup - return - } - - val card = onboardingManager.scanResponse.card - if (!onboardingManager.isActivationStarted(card.cardId)) { - Analytics.send(Onboarding.Started()) - onboardingManager.activationStarted(card.cardId) - } - } - is OnboardingSaltPayAction.OnSwitchedToSaltPayProcess -> { - sendAnalyticsScreenOpened(SaltPayActivationStep.None, newStep = getState().step) - } - is OnboardingSaltPayAction.Update -> { - handleInProgress = true - - val state = getState() - scope.launch { - val updateStep = state.saltPayManager.update(state.step, state.amountToClaim).successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(updateStep, action.withAnalytics)) - - if (updateStep == SaltPayActivationStep.Claim) { - handleClaimRefreshInProgress = true - val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { - SaltPayExceptionHandler.handle(it.error) - handleClaimRefreshInProgress = false - return@launch - } - dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - handleClaimRefreshInProgress = false - } - } - } - is OnboardingSaltPayAction.RegisterCard -> { - handleInProgress = true - val state = getState() - - scope.launch { - val saltPayManager = state.saltPayManager - checkGasIfNeeded(state.saltPayManager, state.step).successOr { - onException(it.error) - return@launch - } - - val pinCode = state.pinCode.guard { - onError(SaltPayActivationError.NeedPin) - return@launch - } - - val attestationResponse = saltPayManager.requestAttestationChallenge().successOr { - onException(it.error) - return@launch - } - - val registrationResponse = tangemSdkManager.runTaskAsync( - runnable = saltPayManager.makeRegistrationTask(attestationResponse.challenge!!), - cardId = saltPayManager.cardId, - initialMessage = Message( - header = null, - body = tangemSdkManager.getString(R.string.registration_task_alert_message), - ), - accessCode = state.accessCode, - ).successOr { - onException(it.error as Exception) - return@launch - } - - saltPayManager.sendTransactions(registrationResponse.signedTransactions).successOr { - onError(SaltPayActivationError.FailedToSendTx) - return@launch - } - - saltPayManager.registerWallet(registrationResponse.attestResponse, pinCode).successOr { - onException(it.error) - return@launch - } - - Analytics.send(Onboarding.PinCodeSet()) - - var nextStep = SaltPayActivationStep.KycIntro - nextStep = saltPayManager.update(nextStep, state.amountToClaim).successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.SetStep(nextStep)) - } - } - is OnboardingSaltPayAction.OpenUtorgKYC -> { - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycStart)) - } - is OnboardingSaltPayAction.UtorgKYCRedirectSuccess -> { - store.dispatch(OnboardingSaltPayAction.RegisterKYC) - } - is OnboardingSaltPayAction.RegisterKYC -> { - val state = getState() - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.KycWaiting)) - handleInProgress = true - - scope.launch { - Timber.d("saltPayManager.registerKYC()") - state.saltPayManager.registerKYC().successOr { - onException(it.error) - return@launch - } - - handleInProgress = false - dispatchOnMain(OnboardingSaltPayAction.Update()) - } - } - is OnboardingSaltPayAction.TrySetPin -> { - try { - assertPinValid(action.pin, getState().pinLength) - Analytics.send(Onboarding.ButtonSetPinCode()) - store.dispatch(OnboardingSaltPayAction.SetPin(action.pin)) - store.dispatch(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.CardRegistration)) - } catch (error: SaltPayActivationError) { - SaltPayExceptionHandler.handle(error) - } - } - is OnboardingSaltPayAction.Claim -> { - val state = getState() - handleInProgress = true - - scope.launch { - val amountToClaim = getAmountToClaimIfNeeded(state.saltPayManager, state.amountToClaim).guard { - onException(SaltPayActivationError.NoFundsToClaim) - return@launch - } - - val scanResponse = store.state.globalState.scanResponse - ?: store.state.globalState.onboardingState.onboardingManager?.scanResponse - val signer = TangemSigner( - card = scanResponse!!.card, - tangemSdk = tangemSdk, - initialMessage = Message(), - accessCode = state.accessCode, - ) { signResponse -> - store.dispatch( - GlobalAction.UpdateWalletSignedHashes( - walletSignedHashes = signResponse.totalSignedHashes, - walletPublicKey = state.saltPayManager.walletPublicKey, - remainingSignatures = signResponse.remainingSignatures, - ), - ) - } - state.saltPayManager.claim(amountToClaim.value!!, signer).successOr { - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.Claim)) - if (it.error is TangemSdkError.UserCancelled) { - handleInProgress = false - } else { - onException(it.error) - } - return@launch - } - - handleInProgress = false - Analytics.send(Onboarding.ClaimWasSuccessfully()) - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimInProgress)) - dispatchOnMain(OnboardingSaltPayAction.RefreshClaim) - } - } - is OnboardingSaltPayAction.RefreshClaim -> { - handleClaimRefreshInProgress = true - - val state = getState() - scope.launch { - val tokenAmountValue = state.saltPayManager.getTokenAmount().successOr { - SaltPayExceptionHandler.handle(it.error) - handleClaimRefreshInProgress = false - return@launch - } - dispatchOnMain(OnboardingSaltPayAction.SetTokenBalance(tokenAmountValue)) - - val amountToClaim = state.saltPayManager.getAmountToClaim().successOr { - if (it.error !is SaltPayActivationError.NoFundsToClaim) { - handleClaimRefreshInProgress = false - SaltPayExceptionHandler.handle(it.error) - return@launch - } else { - null - } - } - handleClaimRefreshInProgress = false - - if (tokenAmountValue.isPositive() && amountToClaim == null) { - dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) - } else { - // dispatchOnMain(OnboardingSaltPayAction.SetStep(SaltPayActivationStep.ClaimSuccess)) - } - } - } - is OnboardingSaltPayAction.SetStep -> { - if (action.withAnalytics) { - sendAnalyticsScreenOpened(getState().step, action.newStep) - } - if (action.newStep == SaltPayActivationStep.ClaimSuccess || - action.newStep == SaltPayActivationStep.Success - ) { - val onboardingManager = getOnboardingManager().guard { - // Null is possible if it is started from a standard pending backup and in this case - // it is impossible to get here - store.dispatchDebugErrorNotification("OnboardingManager can't be NULL") - return - } - val cardIds = gatherCardIds(getOnboardingWalletState().backupState, onboardingManager.scanResponse.card) - finishCardActivation(cardIds) - } - } - else -> { - /* do nothing, only reduce */ - } - } -} - -private fun sendAnalyticsScreenOpened(currentStep: SaltPayActivationStep, newStep: SaltPayActivationStep) { - if (currentStep == newStep) return - - when (newStep) { - SaltPayActivationStep.NeedPin -> Analytics.send(Onboarding.PinScreenOpened()) - SaltPayActivationStep.CardRegistration -> Analytics.send(Onboarding.CardConnectionScreenOpened()) - SaltPayActivationStep.KycIntro -> Analytics.send(Onboarding.KYCScreenOpened()) - SaltPayActivationStep.KycStart -> Analytics.send(Onboarding.KYCStarted()) - SaltPayActivationStep.KycWaiting -> Analytics.send(Onboarding.KYCInProgress()) - SaltPayActivationStep.KycReject -> Analytics.send(Onboarding.KYCRejected()) - SaltPayActivationStep.Claim -> Analytics.send(Onboarding.ClaimScreenOpened()) - SaltPayActivationStep.ClaimSuccess, SaltPayActivationStep.Success -> Analytics.send(Onboarding.Finished()) - else -> {} - } -} - -suspend fun SaltPayActivationManager.update( - currentStep: SaltPayActivationStep, - currentAmountToClaim: Amount?, -): Result { - Timber.d("updateSaltPayStatus") - - val registrationResponse = checkRegistration().successOr { return it } - val amountToClaim = getAmountToClaimIfNeeded(this, currentAmountToClaim) - dispatchOnMain(OnboardingSaltPayAction.SetAmountToClaim(amountToClaim)) - - val newStep = try { - determineStep(currentStep, amountToClaim, registrationResponse) - } catch (ex: Exception) { - return Result.Failure(ex) - } - - checkGasIfNeeded(this, newStep).successOr { return it } - - Timber.d("update: success: %s", newStep) - return Result.Success(newStep) -} - -@Suppress("ComplexMethod") -private fun determineStep( - currentStep: SaltPayActivationStep, - amountToClaim: Amount?, - response: RegistrationResponse.Item, -): SaltPayActivationStep { - fun getStepBaseOnAmountToClaim(amount: Amount?): SaltPayActivationStep = when (amount) { - null -> SaltPayActivationStep.Success - else -> SaltPayActivationStep.Claim - } - - return when { - response.passed != true -> throw SaltPayActivationError.CardNotPassed(response.error) - response.disabledByAdmin == true -> throw SaltPayActivationError.CardDisabled(response.error) - - // go to claim screen - response.active == true -> getStepBaseOnAmountToClaim(amountToClaim) - - // pinSet is false, go toPin screen - response.pinSet == false -> SaltPayActivationStep.NeedPin - - response.kycStatus != null -> { - when (currentStep) { - SaltPayActivationStep.KycWaiting -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - SaltPayActivationStep.KycReject -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycStart - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED -> SaltPayActivationStep.KycStart - KYCStatus.REJECTED -> SaltPayActivationStep.KycStart - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - else -> when (response.kycStatus) { - KYCStatus.NOT_STARTED, KYCStatus.STARTED -> SaltPayActivationStep.KycIntro - KYCStatus.WAITING_FOR_APPROVAL -> SaltPayActivationStep.KycWaiting - KYCStatus.CORRECTION_REQUESTED, KYCStatus.REJECTED -> SaltPayActivationStep.KycReject - KYCStatus.APPROVED -> getStepBaseOnAmountToClaim(amountToClaim) - else -> SaltPayActivationStep.KycIntro - } - } - } - - // kycDate is set, go to kyc waiting screen - response.kycDate != null -> SaltPayActivationStep.KycWaiting - - else -> SaltPayActivationStep.KycIntro - } -} - -private suspend fun getAmountToClaimIfNeeded( - saltPayManager: SaltPayActivationManager, - amountToClaim: Amount?, -): Amount? { - Timber.d("getAmountToClaimIfNeeded: for amount: %s", amountToClaim) - if (amountToClaim != null) { - Timber.d("getAmountToClaimIfNeeded: checks no need, amount already persist") - return amountToClaim - } - - // return Amount(BigDecimal(0.1), Blockchain.SaltPay) - return when (val result = saltPayManager.getAmountToClaim()) { - is Result.Success -> { - Timber.d("getAmountToClaimIfNeeded: success: %s", result.data) - return result.data - } - is Result.Failure -> { - Timber.e(result.error, "getAmountToClaimIfNeeded: failure") - null - } - } -} - -private var handleClaimRefreshInProgress: Boolean = false - set(value) { - field = value - store.dispatchOnMain(OnboardingSaltPayAction.SetClaimRefreshInProgress(value)) - } - -private var handleInProgress: Boolean = false - set(value) { - field = value - store.dispatchOnMain(OnboardingSaltPayAction.SetInProgress(value)) - } - -private suspend fun onError(error: SaltPayActivationError) { - withMainContext { - handleInProgress = false - SaltPayExceptionHandler.handle(error) - } -} - -private suspend fun onException(error: Throwable) { - withMainContext { - handleInProgress = false - SaltPayExceptionHandler.handle(error) - } -} - -private suspend fun checkGasIfNeeded( - saltPayManager: SaltPayActivationManager, - step: SaltPayActivationStep, -): Result { - Timber.d("checkGasIfNeeded: for step: %s", step) - if (step.ordinal >= SaltPayActivationStep.KycIntro.ordinal) { - Timber.d("checkGasIfNeeded: no need to check for step: %s", step) - return Result.Success(Unit) - } - - val result = saltPayManager.checkHasGas() - when (result) { - is Result.Success -> Timber.d("checkGasIfNeeded: has GAS") - is Result.Failure -> Timber.d("checkGasIfNeeded: has NO GAS") - } - return result -} - -@Throws(SaltPayActivationError::class) -private fun assertPinValid(pin: String, pinLength: Int) { - val array = pin.toCharArray() - - if (array.size < pinLength) throw SaltPayActivationError.WeakPin - - val filters: List> = listOf( - AllSymbolsTheSameFilter(), - ) - - if (filters.any { it.filter(pin) }) { - throw SaltPayActivationError.WeakPin - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt deleted file mode 100644 index ad3c493336..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayReducer.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import org.rekotlin.Action - -/** -[REDACTED_AUTHOR] - */ -object OnboardingSaltPayReducer { - fun reduce(action: Action, state: OnboardingWalletState): OnboardingWalletState { - return internalReduce(action, state) - } -} - -@Suppress("ComplexMethod") -private fun internalReduce(anyAction: Action, onboardingWalletState: OnboardingWalletState): OnboardingWalletState { - val action = anyAction as? OnboardingSaltPayAction ?: return onboardingWalletState - - if (onboardingWalletState.onboardingSaltPayState == null && action is OnboardingSaltPayAction.SetDependencies) { - return onboardingWalletState.copy( - onboardingSaltPayState = OnboardingSaltPayState( - saltPayManager = action.registrationManager, - ), - ) - } - - val oldState = onboardingWalletState.onboardingSaltPayState ?: return onboardingWalletState - - val newState = when (action) { - is OnboardingSaltPayAction.SetAccessCode -> { - oldState.copy( - accessCode = action.accessCode, - ) - } - is OnboardingSaltPayAction.SetPin -> { - oldState.copy(pinCode = action.pin) - } - is OnboardingSaltPayAction.SetStep -> { - if (action.newStep == null) { - oldState - } else { - oldState.copy( - step = action.newStep, - ) - } - } - is OnboardingSaltPayAction.SetAmountToClaim -> { - oldState.copy( - amountToClaim = action.amount, - ) - } - is OnboardingSaltPayAction.SetTokenBalance -> { - oldState.copy( - tokenAmount = oldState.tokenAmount.copy(value = action.balanceValue), - ) - } - is OnboardingSaltPayAction.SetInProgress -> { - oldState.copy( - inProgress = action.isInProgress, - ) - } - is OnboardingSaltPayAction.SetClaimRefreshInProgress -> { - oldState.copy( - claimInProgress = action.isInProgress, - ) - } - else -> oldState - } - - return when (newState) { - oldState -> onboardingWalletState - else -> { - onboardingWalletState.copy(onboardingSaltPayState = newState) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt deleted file mode 100644 index 7b68518634..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/redux/OnboardingSaltPayState.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.redux - -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.common.SaltPayWorkaround -import com.tangem.tap.common.toggleWidget.WidgetState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManager -import com.tangem.tap.features.wallet.redux.ProgressState -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class OnboardingSaltPayState( - @Transient - val saltPayManager: SaltPayActivationManager = SaltPayActivationManager.stub(), - val pinCode: String? = null, - val accessCode: String? = null, - val amountToClaim: Amount? = null, - val tokenAmount: Amount = Amount(SaltPayWorkaround.tokenFrom(Blockchain.SaltPay), BigDecimal.ZERO), - val step: SaltPayActivationStep = SaltPayActivationStep.None, - val saltPayCardArtworkUrl: String? = null, - val inProgress: Boolean = false, - val claimInProgress: Boolean = false, -) { - - val mainButtonState: WidgetState - get() = if (inProgress) ProgressState.Loading else ProgressState.Done - - val pinLength: Int = 4 -} - -enum class SaltPayActivationStep { - None, - NoGas, - NeedPin, - CardRegistration, - KycIntro, - KycStart, - KycWaiting, - KycReject, - Claim, - ClaimInProgress, - ClaimSuccess, - Success, -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt deleted file mode 100644 index b4c5bf2c60..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/ui/OnboardingSaltPayStateHandler.kt +++ /dev/null @@ -1,387 +0,0 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.ui - -import android.net.Uri -import android.view.View -import android.view.ViewGroup -import android.view.animation.OvershootInterpolator -import android.widget.ProgressBar -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes -import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.view.children -import androidx.transition.TransitionManager -import coil.load -import coil.size.Scale -import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.button.MaterialButton -import com.tangem.common.extensions.guard -import com.tangem.core.analytics.Analytics -import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.compose.PinCodeWidget -import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.feedback.SupportInfo -import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget -import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget -import com.tangem.tap.common.transitions.InternalNoteLayoutTransition -import com.tangem.tap.features.onboarding.products.wallet.redux.* -import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebChromeClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.UtorgWebViewClient -import com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog.SaltPayDialog -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.SaltPayActivationStep -import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.LayoutOnboardingContainerBottomBinding -import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding -import com.tangem.wallet.databinding.LayoutOnboardingMainBinding - -/** -[REDACTED_AUTHOR] - */ -internal class OnboardingSaltPayStateHandler( - private val walletFragment: OnboardingWalletFragment, -) { - - private val toolbar: MaterialToolbar by lazy { walletFragment.binding.toolbar } - private val claimBinding: LayoutOnboardingMainBinding by lazy { walletFragment.bindingSaltPay.claim } - private val topContainer: LayoutOnboardingContainerTopBinding by lazy { claimBinding.onboardingTopContainer } - private val actionContainer: LayoutOnboardingContainerBottomBinding by lazy { - claimBinding.onboardingActionContainer - } - - private val btnRefreshBalanceWidget by lazy { - RefreshBalanceWidget(claimBinding.onboardingTopContainer.onboardingWalletContainer) - } - - private var progressButton: SaltPayProgressButton? = null - private var previousStep: SaltPayActivationStep = SaltPayActivationStep.None - - fun newState(state: OnboardingWalletState) { - handleCardArtworks(state) - - when (state.step) { - OnboardingWalletStep.SaltPay -> setSaltPayStep(state.onboardingSaltPayState) - OnboardingWalletStep.Backup -> { - if (state.backupState.backupStep == BackupStep.Finished && !state.backupState.isInterruptedBackup) { - // if backup finished -> switch OnboardingWalletStep to the SaltPay - store.dispatch(OnboardingWalletAction.GetToSaltPayStep) - store.dispatch(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess) - } else { - // if not -> back to the standard backup process - walletFragment.handleOnboardingStep(state) - } - } - else -> walletFragment.handleOnboardingStep(state) - } - } - - private fun handleCardArtworks(state: OnboardingWalletState) = with(walletFragment.binding) { - // TODO: SaltPay: remove hardCode - if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - imvFrontCard.load(R.drawable.img_salt_pay_visa) { - scale(Scale.FILL) - crossfade(enable = true) - } - } else { - val uri = Uri.parse(state.onboardingSaltPayState.saltPayCardArtworkUrl) - walletFragment.loadImageIntoImageView(uri, imvFrontCard) - } - imvFirstBackupCard.load(R.drawable.card_placeholder_wallet) - - // if (state.onboardingSaltPayState?.saltPayCardArtworkUrl == null) { - // //if saltPay url not loaded -> load from resource - // val bitmap = BitmapFactory.decodeResource(walletFragment.resources, R.drawable.img_salt_pay_visa) - // imvFrontCard.setImageBitmap(bitmap) - // } else { - // walletFragment.loadImageIntoImageView(state.onboardingSaltPayState.saltPayCardArtworkUrl, imvFrontCard) - // } - // walletFragment.loadImageIntoImageView(state.cardArtworkUrl, imvFirstBackupCard) - } - - fun showAddBackupCards(state: BackupState) = with(walletFragment.binding) { - when (state.backupCardsNumber) { - 0 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_no_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = walletFragment.getText(R.string.onboarding_saltpay_title_one_backup_card) - tvBody.text = walletFragment.getText(R.string.onboarding_saltpay_subtitle_one_backup_card) - } - else -> {} - } - } - - private fun setSaltPayStep(onboardingSaltPayState: OnboardingSaltPayState?) { - val state = onboardingSaltPayState.guard { - store.dispatchDebugErrorNotification("SaltPayView: setSaltPayStep: onboardingSaltPayState = null") - return - } - - walletFragment.binding.onboardingWalletContainer.hide() - walletFragment.bindingSaltPay.onboardingSaltpayContainer.show() - - when (state.step) { - SaltPayActivationStep.NoGas -> handleNoGas() - SaltPayActivationStep.NeedPin -> handleNeedPin() - SaltPayActivationStep.CardRegistration -> handleCardRegistration() - SaltPayActivationStep.KycIntro -> handleKycIntro() - SaltPayActivationStep.KycStart -> handleKycStart(state) - SaltPayActivationStep.KycWaiting -> handleKycWaiting() - SaltPayActivationStep.KycReject -> handleKycReject() - SaltPayActivationStep.Claim -> handleClaim(state) - SaltPayActivationStep.ClaimInProgress -> handleClaim(state) - SaltPayActivationStep.ClaimSuccess -> handleClaim(state) - SaltPayActivationStep.Success -> handleSuccess() - else -> Unit - } - progressButton?.changeState(state.mainButtonState) - } - - private fun handleNoGas() { - // normally this shouldn't happen - store.dispatch(NavigationAction.PopBackTo(AppScreen.Home)) - store.dispatchDialogShow(SaltPayDialog.Activation.NoGas) - } - - private fun handleNeedPin() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_pin) - showOnlyView(pinCode.root) { - progressButton = SaltPayProgressButton(pinCode.root) - progressButton?.mainView?.isEnabled = false - - var enteredPinCode = "" - pinCode.flPinCodeContainer.setContent { - PinCodeWidget { code, isLastSymbolEntered -> - enteredPinCode = code - progressButton?.mainView?.isEnabled = isLastSymbolEntered - } - } - pinCode.btnSetPin.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.TrySetPin(enteredPinCode)) - } - } - } - - private fun handleCardRegistration() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_register_wallet) - showOnlyView(connectCard.root) { - progressButton = SaltPayProgressButton(connectCard.root) - connectCard.btnConnect.setOnClickListener { - Analytics.send(Onboarding.ButtonConnect()) - store.dispatch(OnboardingSaltPayAction.RegisterCard) - } - } - } - - private fun handleKycIntro() = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_start) - showOnlyView(verifyIdentity.root) { - progressButton = SaltPayProgressButton(verifyIdentity.root) - verifyIdentity.btnVerify.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.OpenUtorgKYC) - } - } - } - - private fun handleKycStart(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_start) - showOnlyView(webvVerifyIdentity) { - progressButton = null - webvVerifyIdentity.configureSettings() - webvVerifyIdentity.webViewClient = UtorgWebViewClient( - successUrl = state.saltPayManager.kycUrlProvider.doneUrl, - onSuccess = { - webvVerifyIdentity.stop() - store.dispatch(OnboardingSaltPayAction.UtorgKYCRedirectSuccess) - }, - ) - webvVerifyIdentity.webChromeClient = UtorgWebChromeClient(walletFragment.requireActivity()) - webvVerifyIdentity.loadUrl(state.saltPayManager.kycUrlProvider.requestUrl) - } - } - - private fun handleKycWaiting() = with(walletFragment.bindingSaltPay.kycInProgress) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_progress) - showOnlyView(root) - - imvInProgress.setDrawable(R.drawable.ic_in_progress) - tvHeader.text = getText(R.string.onboarding_title_kyc_waiting) - tvBody.text = getText(R.string.onboarding_subtitle_kyc_waiting) - - btnOpenSupportChat.hide() - btnOpenSupportChat.setOnClickListener { - store.dispatch(GlobalAction.OpenChat(SupportInfo())) - } - - btnKycAction.text = getText(R.string.onboarding_button_kyc_waiting) - btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update()) - } - progressButton = SaltPayProgressButton(root) - } - - private fun handleKycReject() = with(walletFragment.bindingSaltPay.kycInProgress) { - toolbar.title = getText(R.string.onboarding_navbar_kyc_progress) - showOnlyView(root) - - imvInProgress.setDrawable(R.drawable.ic_reject) - tvHeader.text = getText(R.string.onboarding_title_kyc_retry) - tvBody.text = getText(R.string.onboarding_subtitle_kyc_retry) - - btnOpenSupportChat.hide() - btnKycAction.text = getText(R.string.onboarding_button_kyc_start) - btnKycAction.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.Update()) - } - progressButton = SaltPayProgressButton(root) - } - - @Suppress("LongMethod") - private fun handleClaim(state: OnboardingSaltPayState) = with(walletFragment.bindingSaltPay) { - toolbar.title = getText(R.string.onboarding_getting_started) - val btnMain = actionContainer.btnContainer.findViewById(R.id.btn_main_action) - val tvHeader = actionContainer.tvHeader - val tvBody = actionContainer.tvBody - - showOnlyView(claimBinding.root) { - // topContainer.progress.pbState.hide() - progressButton = SaltPayProgressButton(actionContainer.root) - topContainer.imvCardBackground.apply { - setBackgroundDrawable(context.getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - } - topContainer.imvFrontCard.load(R.drawable.img_salt_pay_visa) { - scale(Scale.FILL) - crossfade(enable = true) - } - actionContainer.btnAlternativeAction.hide() - updateConstraints(state.step, R.layout.lp_onboarding_topup_wallet) - } - - btnRefreshBalanceWidget.changeState(if (state.claimInProgress) ProgressState.Loading else ProgressState.Done) - if (btnRefreshBalanceWidget.isShowing != true) { - btnRefreshBalanceWidget.mainView.setOnClickListener { - store.dispatch(OnboardingSaltPayAction.RefreshClaim) - } - } - val tokenAmountString = tokenAmountString(state) - topContainer.onboardingTvBalance.tvBalanceValue.text = tokenAmountString ?: "" - topContainer.onboardingTvBalance.tvBalanceCurrency.text = "" - - when (state.step) { - SaltPayActivationStep.Claim -> { - claimValueString(state)?.let { - tvHeader.text = getText(R.string.onboarding_title_claim, it) - } - tvBody.text = getText(R.string.onboarding_subtitle_claim) - - btnMain.text = getText(R.string.onboarding_button_claim) - btnMain.isEnabled - btnMain.setOnClickListener { - Analytics.send(Onboarding.ButtonClaim()) - store.dispatch(OnboardingSaltPayAction.Claim) - } - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = true - } - SaltPayActivationStep.ClaimInProgress -> { - tvHeader.text = getText(R.string.onboarding_title_claim_progress) - tvBody.text = getText(R.string.onboarding_subtitle_claim_progress) - btnMain.text = getText(R.string.onboarding_button_claim) - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = false - } - SaltPayActivationStep.ClaimSuccess -> { - tvHeader.setText(R.string.common_success) - tvBody.setText(R.string.onboarding_subtitle_success_claim) - - btnMain.setText(R.string.onboarding_button_continue_wallet) - btnMain.setOnClickListener { - walletFragment.showConfetti(false) - store.dispatch(OnboardingWalletAction.FinishOnboarding) - } - - btnRefreshBalanceWidget.mainView.setOnClickListener(null) - updateConstraints(state.step, R.layout.lp_onboarding_done_activation) - walletFragment.showConfetti(true) - progressButton = SaltPayProgressButton(actionContainer.root) - progressButton?.isEnabled = true - } - else -> {} - } - } - - private fun handleSuccess() = with(walletFragment.binding) { - walletFragment.bindingSaltPay.onboardingSaltpayContainer.hide() - onboardingWalletContainer.show() - walletFragment.showSuccess() - tvBody.text = getText(R.string.onboarding_subtitle_success_claim) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet) - } - - private fun tokenAmountString(state: OnboardingSaltPayState): String? { - return state.tokenAmount.value?.toFormattedCurrencyString( - decimals = state.tokenAmount.decimals, - currency = state.tokenAmount.currencySymbol, - ) - } - - private fun claimValueString(state: OnboardingSaltPayState): String? { - return state.amountToClaim?.value?.toFormattedCurrencyString( - decimals = state.amountToClaim.decimals, - currency = state.amountToClaim.currencySymbol, - ) - } - - private fun updateConstraints(currentStep: SaltPayActivationStep, @LayoutRes layoutId: Int) { - if (this.previousStep == currentStep) return - - with(claimBinding.onboardingTopContainer) { - val constraintSet = ConstraintSet() - constraintSet.clone(root.context, layoutId) - constraintSet.applyTo(onboardingWalletContainer) - val transition = InternalNoteLayoutTransition() - transition.interpolator = OvershootInterpolator() - TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition) - } - } - - private fun showOnlyView(view: View, onShowListener: (() -> Unit)? = null) { - walletFragment.bindingSaltPay.onboardingSaltpayContainer.children - .filter { it.id != view.id } - .forEach { it.hide() } - view.show { onShowListener?.invoke() } - } - - private fun getText(@StringRes resId: Int): String { - return walletFragment.getString(resId) - } - - private fun getText(@StringRes resId: Int, vararg formatArgs: Any?): String { - return walletFragment.getString(resId, *formatArgs) - } - - private class SaltPayProgressButton( - root: ViewGroup, - ) : IndeterminateProgressButtonWidget(findButton(root), findProgress(root)) { - companion object { - fun findButton(view: ViewGroup): MaterialButton { - return findContainer(view).children.firstOrNull { it is MaterialButton } as MaterialButton - } - - fun findProgress(view: ViewGroup): View { - return findContainer(view).children.firstOrNull { it is ProgressBar }!! - } - - fun findContainer(view: ViewGroup): ViewGroup { - return view.children.first { it.id == R.id.btn_container } as ViewGroup - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 13c2a3842e..269449599e 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -37,14 +37,11 @@ import com.tangem.tap.features.FragmentOnBackPressedHandler import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.OnboardingMenuProvider import com.tangem.tap.features.onboarding.products.wallet.redux.* -import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction -import com.tangem.tap.features.onboarding.products.wallet.saltPay.ui.OnboardingSaltPayStateHandler import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding -import com.tangem.wallet.databinding.LayoutOnboardingSaltpayBinding import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import dagger.hilt.android.AndroidEntryPoint @@ -62,13 +59,10 @@ class OnboardingWalletFragment : internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer } - internal val bindingSaltPay: LayoutOnboardingSaltpayBinding by lazy { binding.onboardingSaltpayContainer } private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler() private val seedPhraseViewModel by viewModels() - private val saltPayStateHandler: OnboardingSaltPayStateHandler = OnboardingSaltPayStateHandler(this) - private lateinit var cardsWidget: WalletCardsWidget private var accessCodeDialog: AccessCodeDialog? = null @@ -108,7 +102,6 @@ class OnboardingWalletFragment : store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress)) store.dispatch(OnboardingWalletAction.Init) - store.dispatch(OnboardingSaltPayAction.Init) store.dispatch( OnboardingWalletAction.LoadArtwork( cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet), @@ -170,7 +163,6 @@ class OnboardingWalletFragment : override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return - if (state.isSaltPay) reInitCardsWidgetIfNeeded(1) animator.updateBackupState(state.backupState) requireActivity().invalidateOptionsMenu() @@ -182,9 +174,6 @@ class OnboardingWalletFragment : state.wallet2State != null -> { seedPhraseStateHandler.newState(this, state, seedPhraseViewModel) } - state.isSaltPay -> { - saltPayStateHandler.newState(state) - } else -> { loadImageIntoImageView(state.cardArtworkUri, binding.imvFrontCard) loadImageIntoImageView(state.cardArtworkUri, binding.imvFirstBackupCard) @@ -207,7 +196,6 @@ class OnboardingWalletFragment : OnboardingWalletStep.CreateWallet -> setupCreateWalletState() OnboardingWalletStep.Backup -> setBackupState( state = state.backupState, - isSaltPay = state.isSaltPay, isWallet2 = state.wallet2State != null, ) @@ -231,21 +219,21 @@ class OnboardingWalletFragment : animator.setupCreateWalletState() } - private fun setBackupState(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) { + private fun setBackupState(state: BackupState, isWallet2: Boolean) { when (state.backupStep) { - BackupStep.InitBackup -> showBackupIntro(state, isSaltPay, isWallet2) + BackupStep.InitBackup -> showBackupIntro(state, isWallet2) BackupStep.ScanOriginCard -> showScanOriginCard() - BackupStep.AddBackupCards -> showAddBackupCards(state, isSaltPay) + BackupStep.AddBackupCards -> showAddBackupCards(state) BackupStep.SetAccessCode -> showSetAccessCode() BackupStep.EnterAccessCode -> showEnterAccessCode(state) BackupStep.ReenterAccessCode -> showReenterAccessCode(state) - is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state, isSaltPay) - is BackupStep.WriteBackupCard -> showWriteBackupCard(state, isSaltPay) + is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state) + is BackupStep.WriteBackupCard -> showWriteBackupCard(state) BackupStep.Finished -> showSuccess() } } - private fun showBackupIntro(state: BackupState, isSaltPay: Boolean, isWallet2: Boolean) = with(binding) { + private fun showBackupIntro(state: BackupState, isWallet2: Boolean) = with(binding) { imvFirstBackupCard.show() imvSecondBackupCard.show() @@ -260,7 +248,7 @@ class OnboardingWalletFragment : btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup) btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) } - btnWalletAlternativeAction.show(state.canSkipBackup && !isSaltPay && !isWallet2) + btnWalletAlternativeAction.show(state.canSkipBackup && !isWallet2) } animator.showBackupIntro(state) } @@ -281,7 +269,7 @@ class OnboardingWalletFragment : animator.showScanOriginCard() } - private fun showAddBackupCards(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showAddBackupCards(state: BackupState) = with(binding) { prepareBackupView() accessCodeDialog?.dismiss() @@ -299,25 +287,20 @@ class OnboardingWalletFragment : layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup) layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) } layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0 - - if (isSaltPay) { - saltPayStateHandler.showAddBackupCards(state) - } else { - when (state.backupCardsNumber) { - 0 -> { - tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) - } - 1 -> { - tvHeader.text = getText(R.string.onboarding_title_one_backup_card) - tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) - } - 2 -> { - tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) - tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) - } - else -> {} + when (state.backupCardsNumber) { + 0 -> { + tvHeader.text = getText(R.string.onboarding_title_no_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards) } + 1 -> { + tvHeader.text = getText(R.string.onboarding_title_one_backup_card) + tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card) + } + 2 -> { + tvHeader.text = getText(R.string.onboarding_title_two_backup_cards) + tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards) + } + else -> {} } animator.showAddBackupCards(state, state.backupCardsNumber) @@ -363,25 +346,19 @@ class OnboardingWalletFragment : accessCodeDialog?.showError(state.accessCodeError) } - private fun showWritePrimaryCard(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showWritePrimaryCard(state: BackupState) = with(binding) { accessCodeDialog?.dismiss() reInitCardsWidgetIfNeeded(state.backupCardsNumber) prepareViewForFinalizeStep() val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) - if (isSaltPay) { - tvHeader.text = getText(R.string.onboarding_saltpay_title_prepare_origin) - tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_saltpay_button_backup_origin) - } else { - tvHeader.text = getText(R.string.common_origin_card) - tvBody.text = getString( - R.string.onboarding_subtitle_scan_primary_card_format, - state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, - ) - layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin) - } + tvHeader.text = getText(R.string.common_origin_card) + tvBody.text = getString( + R.string.onboarding_subtitle_scan_primary_card_format, + state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) }, + ) + layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_backup_origin) layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) } animator.showWritePrimaryCard(state) @@ -399,27 +376,21 @@ class OnboardingWalletFragment : imvSecondBackupCard.show() } - private fun showWriteBackupCard(state: BackupState, isSaltPay: Boolean) = with(binding) { + private fun showWriteBackupCard(state: BackupState) = with(binding) { prepareViewForFinalizeStep() reInitCardsWidgetIfNeeded(state.backupCardsNumber) val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1 - if (isSaltPay) { - tvHeader.text = getString(R.string.onboarding_saltpay_title_backup_card) - tvBody.text = getString(R.string.onboarding_twins_interrupt_warning) - layoutButtonsCommon.btnWalletMainAction.text = getString(R.string.onboarding_saltpay_title_backup_card) - } else { - val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) - tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) - tvBody.text = getString( - R.string.onboarding_subtitle_scan_backup_card_format, - cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]), - ) - layoutButtonsCommon.btnWalletMainAction.text = getString( - R.string.onboarding_button_backup_card_format, - cardNumber, - ) - } + val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4)) + tvHeader.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) + tvBody.text = getString( + R.string.onboarding_subtitle_scan_backup_card_format, + cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]), + ) + layoutButtonsCommon.btnWalletMainAction.text = getString( + R.string.onboarding_button_backup_card_format, + cardNumber, + ) layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WriteBackupCard(cardNumber)) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt index 36827b96a4..678c2ca2df 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/dialog/InterruptOnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.features.onboarding.products.wallet.saltPay.dialog +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs import android.app.Dialog import android.content.Context diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 8034231609..0883a97fd6 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.ToastNotificationAction @@ -27,9 +28,9 @@ interface SendScreenActionUi : SendScreenAction object ReleaseSendState : Action data class PrepareSendScreen( + val walletManager: WalletManager, val coinAmount: Amount?, val coinRate: BigDecimal?, - val walletManager: WalletManager?, val tokenAmount: Amount? = null, val tokenRate: BigDecimal? = null, ) : SendScreenAction @@ -37,9 +38,9 @@ data class PrepareSendScreen( // Address or PayId sealed class AddressPayIdActionUi : SendScreenActionUi { data class HandleUserInput(val data: String) : AddressPayIdActionUi() - data class PasteAddressPayId(val data: String) : AddressPayIdActionUi() + data class PasteAddressPayId(val data: String, val sourceType: AddressEntered.SourceType) : AddressPayIdActionUi() data class CheckClipboard(val data: String?) : AddressPayIdActionUi() - object CheckAddressPayId : AddressPayIdActionUi() + data class CheckAddressPayId(val sourceType: AddressEntered.SourceType?) : AddressPayIdActionUi() data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi() data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi() data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi() diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt index 81d62770bc..406eacd087 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt @@ -3,20 +3,17 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Wallet import com.tangem.common.services.Result +import com.tangem.core.analytics.Analytics +import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.isPayIdSupported -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction +import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi -import com.tangem.tap.features.send.redux.FeeAction -import com.tangem.tap.features.send.redux.TransactionExtrasAction import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.Dispatchers @@ -33,9 +30,9 @@ internal class AddressPayIdMiddleware { fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) { when (action) { is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch) - is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, dispatch) + is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, action.sourceType, dispatch) is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch) - is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(appState, dispatch) + is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(action.sourceType, appState, dispatch) else -> return } } @@ -44,24 +41,33 @@ internal class AddressPayIdMiddleware { val sendState = appState?.sendState ?: return if (input == sendState.addressPayIdState.viewFieldValue.value) return - setAddressAndCheck(input, true, dispatch) + setAddressAndCheck(data = input, sourceType = null, isUserInput = true, dispatch = dispatch) } - private fun pasteAddressPayId(data: String, dispatch: (Action) -> Unit) { - setAddressAndCheck(data, false, dispatch) + private fun pasteAddressPayId(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) { + setAddressAndCheck(data = data, sourceType = sourceType, isUserInput = false, dispatch = dispatch) } - private fun setAddressAndCheck(data: String, isUserInput: Boolean, dispatch: (Action) -> Unit) { + private fun setAddressAndCheck( + data: String, + sourceType: AddressEntered.SourceType?, + isUserInput: Boolean, + dispatch: (Action) -> Unit, + ) { val potentialPayId = data.lowercase() if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) { dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput)) } else { dispatch(SetWalletAddress(data, isUserInput)) } - dispatch(AddressPayIdActionUi.CheckAddressPayId) + dispatch(AddressPayIdActionUi.CheckAddressPayId(sourceType)) } - private fun verifyAddressPayId(appState: AppState?, dispatch: (Action) -> Unit) { + private fun verifyAddressPayId( + sourceType: AddressEntered.SourceType?, + appState: AppState?, + dispatch: (Action) -> Unit, + ) { val sendState = appState?.sendState ?: return val wallet = sendState.walletManager?.wallet ?: return val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return @@ -70,7 +76,13 @@ internal class AddressPayIdMiddleware { if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) { verifyPayId(addressPayId, wallet, isUserInput, dispatch) } else { - verifyAddress(addressPayId, wallet, isUserInput, dispatch) + verifyAddress( + address = addressPayId, + wallet = wallet, + isUserInput = isUserInput, + dispatch = dispatch, + sourceType = sourceType, + ) } } @@ -112,24 +124,45 @@ internal class AddressPayIdMiddleware { } } - private fun verifyAddress(address: String, wallet: Wallet, isUserInput: Boolean, dispatch: (Action) -> Unit) { + private fun verifyAddress( + address: String, + wallet: Wallet, + sourceType: AddressEntered.SourceType?, + isUserInput: Boolean, + dispatch: (Action) -> Unit, + ) { val addressSchemeSplit = when (wallet.blockchain) { - Blockchain.BitcoinCash, - Blockchain.Kaspa, - -> listOf(address) + Blockchain.BitcoinCash, Blockchain.Kaspa -> listOf(address) else -> address.split(":") } + val noSchemeAddress = when (addressSchemeSplit.size) { 1 -> address // no scheme 2 -> { // scheme if (wallet.blockchain.validateShareScheme(addressSchemeSplit[0])) { addressSchemeSplit[1] } else { + sourceType?.let { + Analytics.send( + event = AddressEntered( + sourceType = sourceType, + validationResult = AddressEntered.ValidationResult.Fail, + ), + ) + } dispatch(SetAddressError(Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN)) return } } else -> { // invalid URI + sourceType?.let { + Analytics.send( + event = AddressEntered( + sourceType = sourceType, + validationResult = AddressEntered.ValidationResult.Fail, + ), + ) + } dispatch(SetAddressError(Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN)) return } @@ -146,9 +179,25 @@ internal class AddressPayIdMiddleware { dispatch(SetWalletAddress(supposedAddress, isUserInput)) dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, null)) dispatch(FeeAction.RequestFee) + sourceType?.let { + Analytics.send( + event = AddressEntered( + sourceType = sourceType, + validationResult = AddressEntered.ValidationResult.Success, + ), + ) + } } else { dispatch(SetAddressError(failReason)) dispatch(TransactionExtrasAction.Release) + sourceType?.let { + Analytics.send( + event = AddressEntered( + sourceType = sourceType, + validationResult = AddressEntered.ValidationResult.Fail, + ), + ) + } } } @@ -187,7 +236,13 @@ internal class AddressPayIdMiddleware { if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) { verifyPayId(addressPayId, wallet, false, internalDispatcher) } else { - verifyAddress(addressPayId, wallet, false, internalDispatcher) + verifyAddress( + address = addressPayId, + wallet = wallet, + sourceType = null, + isUserInput = false, + dispatch = internalDispatcher, + ) } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index 679da154f1..74a835349d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -7,29 +7,23 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.BlockchainSdkError -import com.tangem.blockchain.common.TransactionError -import com.tangem.blockchain.common.TransactionSender -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.domain.models.scan.CardDTO +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.analytics.events.Token.Send.SelectedCurrency.CurrencyType +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -40,25 +34,11 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee -import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.SendActionUi -import com.tangem.tap.features.send.redux.states.ButtonState -import com.tangem.tap.features.send.redux.states.ExternalTransactionData -import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import com.tangem.tap.userWalletsListManager -import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -66,7 +46,7 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -import java.util.* +import java.util.EnumSet /** [REDACTED_AUTHOR] @@ -119,11 +99,11 @@ private fun verifyAndSendTransaction( val card = appState.globalState.scanResponse?.card ?: return val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return val typedAmount = sendState.amountState.amountToExtract ?: return - val currentFee = sendState.feeState.currentFee ?: return + val fee = sendState.feeState.currentFee ?: return val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend()) - val transactionErrors = walletManager.validateTransaction(amountToSend, currentFee.amount) + val transactionErrors = walletManager.validateTransaction(amountToSend, fee.amount) when { transactionErrors.contains(TransactionError.TezosSendAll) -> { val reduceAmount = walletManager.wallet.blockchain.minimalAmount() @@ -135,9 +115,17 @@ private fun verifyAndSendTransaction( }, sendAllCallback = { sendTransaction( - action, walletManager, amountToSend, currentFee, destinationAddress, - sendState.transactionExtrasState, card, sendState.externalTransactionData, - dispatch, + action = action, + walletManager = walletManager, + amountToSend = amountToSend, + fee = fee, + feeType = sendState.feeState.selectedFeeType, + destinationAddress = destinationAddress, + transactionExtras = sendState.transactionExtrasState, + card = card, + externalTransactionData = sendState.externalTransactionData, + mainCurrencyType = sendState.amountState.mainCurrency.type, + dispatch = dispatch, ) }, reduceAmount, @@ -146,8 +134,17 @@ private fun verifyAndSendTransaction( } else -> { sendTransaction( - action, walletManager, amountToSend, currentFee, destinationAddress, - sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch, + action = action, + walletManager = walletManager, + amountToSend = amountToSend, + fee = fee, + feeType = sendState.feeState.selectedFeeType, + destinationAddress = destinationAddress, + transactionExtras = sendState.transactionExtrasState, + card = card, + externalTransactionData = sendState.externalTransactionData, + mainCurrencyType = sendState.amountState.mainCurrency.type, + dispatch = dispatch, ) } } @@ -159,10 +156,12 @@ private fun sendTransaction( walletManager: WalletManager, amountToSend: Amount, fee: Fee, + feeType: FeeType, destinationAddress: String, transactionExtras: TransactionExtrasState, card: CardDTO, externalTransactionData: ExternalTransactionData?, + mainCurrencyType: MainCurrencyType, dispatch: (Action) -> Unit, ) { dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS)) @@ -244,17 +243,37 @@ private fun sendTransaction( dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) tangemSdk.config.linkedTerminal = linkedTerminalState - val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend) when (sendResult) { is SimpleResult.Success -> { - Analytics.send(Token.Send.TransactionSent(currencyType)) dispatch(SendAction.SendSuccess) if (externalTransactionData != null) { - Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Sell)) + Analytics.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Sell, + memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty, + ), + ) + Analytics.send( + Token.Send.SelectedCurrency( + currency = when (mainCurrencyType) { + MainCurrencyType.FIAT -> CurrencyType.AppCurrency + MainCurrencyType.CRYPTO -> CurrencyType.AppCurrency + }, + ), + ) dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) } else { - Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Send)) + Analytics.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Send( + blockchain = walletManager.wallet.blockchain.fullName, + token = amountToSend.currencySymbol, + feeType = feeType.convertToAnalyticsFeeType(), + ), + memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty, + ), + ) dispatch(NavigationAction.PopBackTo()) } scope.launch(Dispatchers.IO) { @@ -272,8 +291,6 @@ private fun sendTransaction( ) val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext - Analytics.send(Token.Send.TransactionSent(currencyType, error)) - when (error) { is BlockchainSdkError.WrappedTangemError -> { val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt index 48eafac40f..4134e8c148 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt @@ -85,7 +85,7 @@ private class EmptyReducer : SendInternalReducer { private class PrepareSendScreenStatesReducer : SendInternalReducer { override fun handle(action: SendScreenAction, sendState: SendState): SendState { val prepareAction = action as PrepareSendScreen - val walletManager = action.walletManager!! + val walletManager = action.walletManager val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!! val decimals = amountToExtract.decimals val feePaidInNetworkCurrency = isFeePaidInNetworkCurrency(walletManager.wallet.blockchain) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index 4fd9516f9a..1ae163571a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.states import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.features.wallet.redux.ProgressState import java.math.BigDecimal @@ -10,7 +11,16 @@ import java.math.BigDecimal [REDACTED_AUTHOR] */ enum class FeeType { - SINGLE, LOW, NORMAL, PRIORITY + SINGLE, LOW, NORMAL, PRIORITY; +} + +fun FeeType.convertToAnalyticsFeeType(): AnalyticsParam.FeeType { + return when (this) { + FeeType.SINGLE -> AnalyticsParam.FeeType.Fixed + FeeType.LOW -> AnalyticsParam.FeeType.Min + FeeType.NORMAL -> AnalyticsParam.FeeType.Normal + FeeType.PRIORITY -> AnalyticsParam.FeeType.Max + } } data class FeeState( diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt new file mode 100644 index 0000000000..d3b563025d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.send.ui + +import android.R +import android.content.Context +import android.util.AttributeSet +import com.google.android.material.textfield.TextInputEditText + +class EditTextCustomPaste @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : TextInputEditText(context, attrs, defStyleAttr) { + + private var onSystemPasteButtonClickListener: (() -> Unit)? = null + + fun setOnSystemPasteButtonClickListener(callback: () -> Unit) { + onSystemPasteButtonClickListener = callback + } + + override fun onTextContextMenuItem(id: Int): Boolean { + val isConsumed = super.onTextContextMenuItem(id) + + if (id == R.id.paste) onSystemPasteButtonClickListener?.invoke() + + return isConsumed + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index 0422cd1bb5..64c4fc53a7 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -32,25 +32,10 @@ import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget import com.tangem.tap.common.toggleWidget.ViewStateWidget import com.tangem.tap.features.BaseStoreFragment import com.tangem.tap.features.addBackPressHandler -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.CheckClipboard -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.PasteAddressPayId -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.SetTruncateHandler -import com.tangem.tap.features.send.redux.AddressPayIdActionUi.TruncateOrRestore -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi -import com.tangem.tap.features.send.redux.AmountActionUi.CheckAmountToSend -import com.tangem.tap.features.send.redux.AmountActionUi.SetMainCurrency -import com.tangem.tap.features.send.redux.AmountActionUi.SetMaxAmount -import com.tangem.tap.features.send.redux.AmountActionUi.ToggleMainCurrency -import com.tangem.tap.features.send.redux.FeeActionUi.ChangeIncludeFee -import com.tangem.tap.features.send.redux.FeeActionUi.ChangeSelectedFee -import com.tangem.tap.features.send.redux.FeeActionUi.ToggleControlsVisibility -import com.tangem.tap.features.send.redux.ReceiptAction -import com.tangem.tap.features.send.redux.ReleaseSendState -import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.SendActionUi -import com.tangem.tap.features.send.redux.TransactionExtrasAction +import com.tangem.tap.features.send.redux.* +import com.tangem.tap.features.send.redux.AddressPayIdActionUi.* +import com.tangem.tap.features.send.redux.AmountActionUi.* +import com.tangem.tap.features.send.redux.FeeActionUi.* import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.MainCurrencyType import com.tangem.tap.features.send.ui.stateSubscribers.SendStateSubscriber @@ -62,12 +47,7 @@ import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.* import java.text.DecimalFormatSymbols private const val EDIT_TEXT_INPUT_DEBOUNCE = 400L @@ -123,20 +103,37 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") }) store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString())) - etAddressOrPayId.setOnFocusChangeListener { _, hasFocus -> - store.dispatch(TruncateOrRestore(!hasFocus)) - } - etAddressOrPayId.inputtedTextAsFlow() - .debounce(EDIT_TEXT_INPUT_DEBOUNCE) - .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } - .onEach { - store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + etAddressOrPayId.apply { + setOnSystemPasteButtonClickListener { + store.dispatch( + PasteAddressPayId( + data = requireContext().getFromClipboard()?.toString() ?: "", + sourceType = Token.Send.AddressEntered.SourceType.PastePopup, + ), + ) } - .launchIn(mainScope) + + setOnFocusChangeListener { _, hasFocus -> + store.dispatch(TruncateOrRestore(!hasFocus)) + } + + inputtedTextAsFlow() + .debounce(EDIT_TEXT_INPUT_DEBOUNCE) + .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it } + .onEach { + store.dispatch(AddressPayIdActionUi.HandleUserInput(it)) + } + .launchIn(mainScope) + } imvPaste.setOnClickListener { Analytics.send(Token.Send.ButtonPaste()) - store.dispatch(PasteAddressPayId(requireContext().getFromClipboard()?.toString() ?: "")) + store.dispatch( + PasteAddressPayId( + data = requireContext().getFromClipboard()?.toString() ?: "", + sourceType = Token.Send.AddressEntered.SourceType.PasteButton, + ), + ) store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused)) } imvQrCode.setOnClickListener { @@ -207,7 +204,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { // inserting an incorrect amount by shareUri binding.lSendAddressPayid.imvQrCode.postDelayed( { - store.dispatch(PasteAddressPayId(scannedCode)) + store.dispatch( + PasteAddressPayId( + data = scannedCode, + sourceType = Token.Send.AddressEntered.SourceType.QRCode, + ), + ) store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused)) }, 200, diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index 9ecb139cae..104ebf9d8c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -21,6 +22,9 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState import kotlinx.collections.immutable.ImmutableCollection +/** This const configures how many items show to user and hide more than*/ +private const val MAX_VISIBLE_BRIEF_ICONS = 9 + /** [REDACTED_AUTHOR] */ @@ -37,10 +41,25 @@ internal fun BriefNetworksList( exit = fadeOut(), ) { Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - networks.forEach { network -> - key(network.name + network.protocolName) { - BriefNetworkItem(model = network) + val iterator = networks.iterator() + var index = 0 + while (iterator.hasNext()) { + val network = iterator.next() + if (index < MAX_VISIBLE_BRIEF_ICONS) { + key(network.name + network.protocolName) { + BriefNetworkItem(model = network) + } + } else { + if (networks.size < MAX_VISIBLE_BRIEF_ICONS + 1) { + key(network.name + network.protocolName) { + BriefNetworkItem(model = network) + } + } else { + HasMoreItem(moreCount = networks.size - index) + break + } } + index++ } } } @@ -50,8 +69,8 @@ internal fun BriefNetworksList( [REDACTED_AUTHOR] */ @Composable -internal fun BriefNetworkItem(model: NetworkItemState) { - Box(modifier = Modifier.size(size = TangemTheme.dimens.size20)) { +internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modifier) { + Box(modifier = modifier.size(size = TangemTheme.dimens.size20)) { Image( painter = painterResource(id = model.iconResId.value), contentDescription = null, @@ -76,4 +95,21 @@ internal fun BriefNetworkItem(model: NetworkItemState) { } } } +} + +@Composable +internal fun HasMoreItem(moreCount: Int) { + Box( + modifier = Modifier + .size(size = TangemTheme.dimens.size20) + .clip(CircleShape) + .background(TangemTheme.colors.control.unchecked), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "+$moreCount", + style = TangemTheme.typography.overline, + color = TangemTheme.colors.text.tertiary, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index db13376244..8db98a4ee0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -98,7 +98,7 @@ sealed class WalletAction : Action { val swapAllowed: Boolean, ) : DialogAction() - data class ChooseCurrency(val amounts: List?) : DialogAction() + data class ChooseCurrency(val amounts: List) : DialogAction() data class RussianCardholdersWarningDialog( val dialogData: WalletDialog.RussianCardholdersWarningDialog.Data? = null, ) : DialogAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 1dde548a55..9c1feed6b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -132,6 +132,5 @@ data class Artwork( const val MARTA_CARD_ID = "BC02" const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" - const val SALT_PAY_URL = "key_for_switch_url_to_drawableId_of_salt_pay_card" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 6020f4c151..0c56d30f51 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import androidx.core.os.bundleOf +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.common.AmountType import com.tangem.common.extensions.guard @@ -129,13 +130,16 @@ class TradeCryptoMiddleware { val selectedWalletData = store.state.walletState.selectedWalletData ?: return Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(selectedWalletData.currency))) + val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency).guard { + FirebaseCrashlytics.getInstance().recordException(IllegalStateException("WalletManager is null")) + return + } - val walletManager = store.state.walletState.getWalletManager(selectedWalletData.currency) store.dispatchOnMain( PrepareSendScreen( - coinAmount = walletManager?.wallet?.amounts?.get(AmountType.Coin), - coinRate = selectedWalletData.fiatRate, walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = selectedWalletData.fiatRate, ), ) store.dispatchOnMain( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index c21a15ce98..b381ec0365 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -37,6 +37,8 @@ class WalletDialogsMiddleware { ) } is WalletAction.DialogAction.ChooseCurrency -> { + if (action.amounts.isEmpty()) return + store.dispatchDialogShow( WalletDialog.SelectAmountToSendDialog( amounts = action.amounts, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index b71a2eccc9..c7bde94141 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.redux.middlewares import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.common.CompletionResult import com.tangem.common.doOnSuccess @@ -32,7 +33,6 @@ import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.ifActive -import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -147,26 +147,76 @@ class WalletMiddleware { store.dispatchOpenUrl(action.exploreUrl) } is WalletAction.Send -> { + val walletStore = walletState.getWalletStore(walletState.selectedCurrency) + val selectedWalletData = walletState.selectedWalletData + val walletManager = walletStore?.walletManager + + if (walletStore == null || walletManager == null || selectedWalletData == null) { + val error = TapError.UnsupportedState( + "WalletAction.Send: walletStore or selectedWalletData or walletManager is null", + ) + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return + } if (!networkConnectionManager.isOnline) { store.dispatchErrorNotification(TapError.NoInternetConnection) return } - val newAction = prepareSendAction(action.amount, store.state.walletState) - if (newAction is PrepareSendScreen && newAction.walletManager == null) { - store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Home)) - FirebaseCrashlytics.getInstance().recordException( - IllegalStateException("PrepareSendScreen: walletManager is null"), - ) - store.dispatchToastNotification(R.string.internal_error_wallet_manager_not_found) - } else { - store.dispatch(newAction) - if (newAction is PrepareSendScreen) { - store.state.walletState.selectedWalletData?.currency?.let { currency -> - Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency))) - } - store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) + + val currency = selectedWalletData.currency + val initSendStateAction = if (action.amount == null) { + val sendableAmounts = walletManager.wallet.getSendableAmounts() + if (sendableAmounts.isEmpty()) { + val error = TapError.UnsupportedState("WalletAction.Send: Nothing to send") + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return } + + if (walletState.isMultiwalletAllowed) { + val amountToSend = sendableAmounts.find { it.currencySymbol == currency.currencySymbol } + if (amountToSend == null) { + val error = TapError.UnsupportedState("WalletAction.Send: Amount to send is null") + FirebaseCrashlytics.getInstance().recordException(IllegalStateException(error.stateError)) + store.dispatchErrorNotification(error) + return + } + + makeInitSendStateActionByCurrency( + currency = currency, + amount = amountToSend, + walletStore = walletStore, + walletManager = walletManager, + selectedWalletData = selectedWalletData, + ) + } else { + val isSingleAmount = sendableAmounts.size == 1 + if (isSingleAmount) { + makeInitSendStateActionByAmount( + amount = sendableAmounts.first(), + walletStore = walletStore, + walletManager = walletManager, + selectedWalletData = selectedWalletData, + ) + } else { + store.dispatch(WalletAction.DialogAction.ChooseCurrency(sendableAmounts)) + return + } + } + } else { + // action.amount received from the ChooseCurrency dialog + makeInitSendStateActionByAmount( + amount = action.amount, + walletManager = walletManager, + walletStore = walletStore, + selectedWalletData = selectedWalletData, + ) } + + Analytics.send(Token.ButtonSend(AnalyticsParam.CurrencyType.Currency(currency))) + store.dispatch(initSendStateAction) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) } is WalletAction.ShowSaveWalletIfNeeded -> { showSaveWalletIfNeeded() @@ -222,6 +272,59 @@ class WalletMiddleware { } } + private fun makeInitSendStateActionByAmount( + amount: Amount, + walletStore: WalletStoreModel, + walletManager: WalletManager, + selectedWalletData: WalletDataModel, + ): PrepareSendScreen = when (amount.type) { + AmountType.Coin -> + PrepareSendScreen( + walletManager = walletManager, + coinAmount = amount, + coinRate = selectedWalletData.fiatRate, + ) + is AmountType.Token -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = walletStore.blockchainWalletData.fiatRate, + tokenAmount = amount, + tokenRate = selectedWalletData.fiatRate, + ) + } + AmountType.Reserve -> { + val exception = IllegalStateException("WalletAction.Send: Reserve can't be sent") + FirebaseCrashlytics.getInstance().recordException(exception) + throw exception + } + } + + private fun makeInitSendStateActionByCurrency( + currency: Currency, + amount: Amount, + walletStore: WalletStoreModel, + walletManager: WalletManager, + selectedWalletData: WalletDataModel, + ): PrepareSendScreen = when (currency) { + is Currency.Blockchain -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = amount, + coinRate = selectedWalletData.fiatRate, + ) + } + is Currency.Token -> { + PrepareSendScreen( + walletManager = walletManager, + coinAmount = walletManager.wallet.amounts[AmountType.Coin], + coinRate = walletStore.blockchainWalletData.fiatRate, + tokenAmount = amount, + tokenRate = selectedWalletData.fiatRate, + ) + } + } + private fun changeSelectedWalletAddress(type: AddressType, state: WalletState) { val selectedUserWalletId = userWalletsListManager.selectedUserWalletSync?.walletId.guard { Timber.e("Unable to change selected wallet address, no user wallet selected") @@ -284,72 +387,4 @@ class WalletMiddleware { } } } - - private fun prepareSendAction(amount: Amount?, state: WalletState?): Action { - val selectedWalletData = state?.selectedWalletData - val currency = selectedWalletData?.currency - val walletStore = state?.getWalletStore(currency) - - return if (amount != null) { - if (amount.type is AmountType.Token) { - prepareSendActionForToken(amount, selectedWalletData, walletStore) - } else { - PrepareSendScreen(amount, selectedWalletData?.fiatRate, walletStore?.walletManager) - } - } else { - val amounts = walletStore?.walletManager?.wallet?.getSendableAmounts() - if (currency != null && state.isMultiwalletAllowed) { - when (currency) { - is Currency.Blockchain -> { - val amountToSend = amounts?.find { it.currencySymbol == currency.blockchain.currency } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) - PrepareSendScreen( - coinAmount = amountToSend, - coinRate = selectedWalletData.fiatRate, - walletManager = walletStore.walletManager, - ) - } - is Currency.Token -> { - val amountToSend = amounts?.find { it.currencySymbol == currency.token.symbol } - ?: return WalletAction.DialogAction.ChooseCurrency(amounts) - prepareSendActionForToken( - amount = amountToSend, - selectedWalletData = selectedWalletData, - walletStore = walletStore, - ) - } - } - } else { - val amountsSize = amounts?.size ?: 0 - if (amountsSize > 1) { - WalletAction.DialogAction.ChooseCurrency(amounts) - } else { - val amountToSend = amounts?.first() - PrepareSendScreen( - coinAmount = amountToSend, - coinRate = selectedWalletData?.fiatRate, - walletManager = walletStore?.walletManager, - ) - } - } - } - } - - private fun prepareSendActionForToken( - amount: Amount, - selectedWalletData: WalletDataModel?, - walletStore: WalletStoreModel?, - ): PrepareSendScreen { - val coinRate = walletStore?.blockchainWalletData?.fiatRate - val tokenRate = selectedWalletData?.fiatRate - val coinAmount = walletStore?.walletManager?.wallet?.amounts?.get(AmountType.Coin) - - return PrepareSendScreen( - coinAmount = coinAmount, - coinRate = coinRate, - walletManager = walletStore?.walletManager, - tokenAmount = amount, - tokenRate = tokenRate, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt index c772684c48..88fbb86932 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/models/WalletDialog.kt @@ -6,7 +6,7 @@ import com.tangem.tap.common.redux.StateDialog import com.tangem.wallet.R sealed interface WalletDialog : StateDialog { - data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog + data class SelectAmountToSendDialog(val amounts: List) : WalletDialog object SignedHashesMultiWalletDialog : WalletDialog data class ChooseTradeActionDialog( val buyAllowed: Boolean, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index b15b018e09..47d07b91b0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -24,7 +24,6 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.datasource.connection.NetworkConnectionManager -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.feature.swap.api.SwapFeatureToggleManager import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.tap.MainActivity @@ -47,7 +46,6 @@ import com.tangem.tap.features.wallet.ui.adapters.WarningMessagesAdapter import com.tangem.tap.features.wallet.ui.wallet.MultiWalletView import com.tangem.tap.features.wallet.ui.wallet.SingleWalletView import com.tangem.tap.features.wallet.ui.wallet.WalletView -import com.tangem.tap.features.wallet.ui.wallet.saltPay.SaltPayWalletView import com.tangem.tap.store import com.tangem.wallet.BuildConfig import com.tangem.wallet.R @@ -166,20 +164,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber { - walletView.onViewDestroy() - walletView = SaltPayWalletView() - walletView.changeWalletView(this, binding) - } state.isMultiwalletAllowed && walletView !is MultiWalletView -> { walletView.onViewDestroy() walletView = MultiWalletView() walletView.changeWalletView(this, binding) } - !state.isMultiwalletAllowed && !isSaltPay && walletView !is SingleWalletView -> { + !state.isMultiwalletAllowed && walletView !is SingleWalletView -> { walletView.onViewDestroy() walletView = SingleWalletView() walletView.changeWalletView(this, binding) @@ -199,9 +190,9 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber(DiffUtilCallback) { +private class ChooseAmountAdapter : ListAdapter(DiffUtilCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AmountViewHolder { val binding = ItemWalletAmountToSendBinding.inflate( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt index dc44583536..6c12a9f65c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SimpleOkDialog.kt @@ -53,4 +53,17 @@ object SimpleOkDialog { ), context = context, ) + + fun create(dialog: AppDialog.OkCancelDialogRes, context: Context): AlertDialog { + return AlertDialog.Builder(context).apply { + setTitle(context.getString(dialog.headerId)) + setMessage(dialog.messageId) + setPositiveButton(dialog.okButton.title) { _, _ -> dialog.okButton.action?.invoke() } + setNegativeButton(dialog.cancelButton.title) { _, _ -> dialog.cancelButton.action?.invoke() } + setOnDismissListener { + store.dispatchDialogHide() + dialog.cancelButton.action?.invoke() + } + }.create() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 269f0ec071..421393fcb5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -73,7 +73,6 @@ class MultiWalletView : WalletView() { private fun showMultiWalletView(binding: FragmentWalletBinding) = with(binding) { watcher.clear() - lSaltPayWallet.root.hide() tvTwinCardNumber.hide() rvPendingTransaction.hide() lCardBalance.root.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index 5d49193785..a97a94e9f3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -39,7 +39,6 @@ class SingleWalletView : WalletView() { private fun showSingleWalletView(binding: FragmentWalletBinding) = with(binding) { watchedPrimaryWalletForAddressCard = null watchedPrimaryWalletForBalance = null - lSaltPayWallet.root.hide() tvTwinCardNumber.hide() rvMultiwallet.hide() btnAddToken.hide() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt deleted file mode 100644 index ef9bf43ffa..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/SaltPayWalletView.kt +++ /dev/null @@ -1,244 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay - -import android.view.LayoutInflater -import android.view.ViewGroup -import com.tangem.core.analytics.Analytics -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.common.ShimmerData -import com.tangem.tap.common.ShimmerRecyclerAdapter -import com.tangem.tap.common.analytics.events.MainScreen -import com.tangem.tap.common.extensions.animateVisibility -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.recyclerView.SpaceItemDecoration -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.ui.utils.getFormattedAmount -import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount -import com.tangem.tap.features.wallet.ui.utils.isAvailableToBuy -import com.tangem.tap.features.wallet.ui.WalletFragment -import com.tangem.tap.features.wallet.ui.wallet.WalletView -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryItemData -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.HistoryTransactionData -import com.tangem.tap.features.wallet.ui.wallet.saltPay.rv.TxHistoryAdapter -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.wallet.databinding.FragmentWalletBinding -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryShimmerBinding -import com.tangem.wallet.databinding.LayoutSaltPayBalanceBinding -import com.tangem.wallet.databinding.LayoutSaltPayTxHistoryBinding -import com.tangem.wallet.databinding.LayoutSaltPayWalletBinding -import kotlinx.coroutines.launch -import timber.log.Timber - -/** -[REDACTED_AUTHOR] - */ -class SaltPayWalletView : WalletView() { - - private var saltPayBinding: LayoutSaltPayWalletBinding? = null - - private val balanceWidget: LayoutSaltPayBalanceBinding? - get() = saltPayBinding?.lSaltPayBalance - - private val txWidget: LayoutSaltPayTxHistoryBinding? - get() = saltPayBinding?.lSaltPayTxHistory - - private val itemDecorator = SpaceItemDecoration.vertical(10F) - - override fun changeWalletView(fragment: WalletFragment, binding: FragmentWalletBinding) { - Timber.d("changeWalletView") - saltPayBinding = binding.lSaltPayWallet - showSaltPayView(binding) - onViewCreated() - } - - override fun onViewCreated() { - Timber.d("onViewCreated") - prepareTxRecyclers(txWidget!!) - } - - private fun showSaltPayView(binding: FragmentWalletBinding) = with(binding) { - Timber.d("showSaltPayView") - rvWarningMessages.hide() - rvPendingTransaction.hide() - rvMultiwallet.hide() - tvTwinCardNumber.hide() - lCardBalance.root.hide() - lSingleWalletBalance.root.hide() - lAddress.root.hide() - rowButtons.hide() - btnAddToken.hide() - pbLoadingUserTokens.hide() - lSaltPayWallet.root.show() - } - - private fun prepareTxRecyclers(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("prepareTxRecyclers") - val vhViewFactory: (ViewGroup) -> ViewGroup = { - val inflater = LayoutInflater.from(it.context) - ItemSaltPayTxHistoryShimmerBinding.inflate(inflater, it, false).root - } - val adapter = ShimmerRecyclerAdapter(vhViewFactory) - rvTxHistoryShimmer.adapter = adapter - rvTxHistoryShimmer.addItemDecoration(itemDecorator) - adapter.submitList( - listOf( - ShimmerData(), - ShimmerData(), - ShimmerData(), - ), - ) - - rvTxHistory.adapter = TxHistoryAdapter() - rvTxHistory.addItemDecoration(itemDecorator) - } - - override fun onNewState(state: WalletState) { - val balanceWidget = balanceWidget ?: return - val txWidget = txWidget ?: return - - val exchangeManager = store.state.globalState.exchangeManager - setupBalanceWidget(balanceWidget, exchangeManager, state) - setupTxHistoryWidget(txWidget, state) - } - - private fun setupBalanceWidget( - balanceWidget: LayoutSaltPayBalanceBinding, - exchangeManager: CurrencyExchangeManager, - state: WalletState, - ) = with(balanceWidget) { - Timber.d("setupBalanceWidget") - val tokenData = state.primaryTokenData ?: return - - val appCurrency = store.state.globalState.appCurrency - val mainProgressState = state.state - - if (mainProgressState == ProgressState.Loading) { - veilBalance.veil() - veilBalanceCrypto.veil() - } else { - veilBalanceCrypto.unVeil() - } - - tvUnreachable.animateVisibility(show = mainProgressState == ProgressState.Error) - veilBalanceCrypto.animateVisibility(show = mainProgressState != ProgressState.Error) - - if (tokenData.fiatRate == null) { - veilBalance.veil() - } else { - veilBalance.unVeil() - tvBalance.text = tokenData.getFormattedFiatAmount(appCurrency) - } - - tvBalanceCrypto.text = tokenData.getFormattedAmount() - - tvCurrencyName.text = appCurrency.code - tvCurrencyName.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) - } - - btnBuy.show(tokenData.isAvailableToBuy(exchangeManager)) - btnBuy.setOnClickListener { - Analytics.send(MainScreen.ButtonBuy()) - store.dispatch(WalletAction.TradeCryptoAction.Buy(false)) - } - } - - private fun setupTxHistoryWidget(txWidget: LayoutSaltPayTxHistoryBinding, state: WalletState) { - Timber.d("setupTxHistoryWidget") - if (state.state == ProgressState.Loading) { - handleTxLoading(txWidget) - return - } - if (state.state == ProgressState.Error) { - handleTxError(txWidget) - return - } - if (state.state != ProgressState.Done) return - val walletAddress = state.primaryWalletManager?.wallet?.address ?: return - val tokenData = state.primaryTokenData ?: return - val historyTransactions = tokenData.historyTransactions ?: return - if (historyTransactions.isEmpty()) { - handleTxEmpty(txWidget) - return - } - - scope.launch { - val dateAssociatedHistory = mutableListOf>>() - historyTransactions.forEach { tx -> - val txHistoryData = HistoryTransactionData(tx, walletAddress) - val item = dateAssociatedHistory.firstOrNull { it.first == txHistoryData.date } - if (item == null) { - dateAssociatedHistory.add(Pair(txHistoryData.date, mutableListOf(txHistoryData))) - } else { - item.second.add(txHistoryData) - } - } - - val rvDataList = mutableListOf() - dateAssociatedHistory.forEach { (txData, txHistoryData) -> - rvDataList.add(HistoryItemData.Date(txData)) - rvDataList.addAll(txHistoryData.map { HistoryItemData.TransactionData(it) }) - } - - withMainContext { handleTxSuccess(txWidget, rvDataList) } - } - } - - private fun handleTxLoading(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxLoading") - groupEmpty.hide() - groupError.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - groupShimmer.show() - } - - private fun handleTxSuccess(txWidget: LayoutSaltPayTxHistoryBinding, dataList: List) = - with(txWidget) { - Timber.d("handleTxSuccess") - groupShimmer.hide() - groupEmpty.hide() - groupError.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, dataList) - groupSuccess.show() - } - - private fun handleTxEmpty(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxEmpty") - groupShimmer.hide() - groupError.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, emptyList()) - groupEmpty.show() - } - - private fun handleTxError(txWidget: LayoutSaltPayTxHistoryBinding) = with(txWidget) { - Timber.d("handleTxError") - groupShimmer.hide() - groupEmpty.hide() - groupSuccess.hide() - // root.beginDelayedTransition() - updateTxHistoryWidget(txWidget, emptyList()) - groupError.show() - } - - private fun updateTxHistoryWidget(txWidget: LayoutSaltPayTxHistoryBinding, dataList: List) = - with(txWidget) { - (rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList) - } - - override fun onViewDestroy() { - Timber.d("onViewDestroy") - txWidget?.apply { - rvTxHistoryShimmer.removeItemDecoration(itemDecorator) - rvTxHistory.removeItemDecoration(itemDecorator) - } - super.onViewDestroy() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt deleted file mode 100644 index 7f194e047e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/HistoryItemData.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.TransactionStatus -import com.tangem.common.extensions.guard -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.features.wallet.models.PendingTransactionType -import java.text.SimpleDateFormat -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -sealed class HistoryItemData( - val viewType: Int, - val itemId: Long, -) { - data class Date(val date: String) : HistoryItemData(0, date.hashCode().toLong()) - data class TransactionData(val data: HistoryTransactionData) : HistoryItemData(1, data.address.hashCode().toLong()) -} - -data class HistoryTransactionData( - private val transactionData: TransactionData, - private val walletAddress: String, -) { - - val isInProgress: Boolean = transactionData.status == TransactionStatus.Unconfirmed - - val transactionType: PendingTransactionType = when { - transactionData.sourceAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Outgoing - transactionData.destinationAddress.lowercase() == walletAddress.lowercase() -> PendingTransactionType.Incoming - else -> PendingTransactionType.Unknown - } - - val address: String = when (transactionType) { - PendingTransactionType.Incoming -> transactionData.sourceAddress.reduceAddress() - PendingTransactionType.Outgoing -> transactionData.destinationAddress.reduceAddress() - PendingTransactionType.Unknown -> "Unknown address" - } - - val time: String = transactionData.date?.let { - SimpleDateFormat("HH:mm").format(it.time) - } ?: "00:00" - - val date: String = calculateDate() - - val txSign: String = when (transactionType) { - PendingTransactionType.Incoming -> "+" - PendingTransactionType.Outgoing -> "-" - PendingTransactionType.Unknown -> "" - } - - val amountValue: String = transactionData.amount.value - ?.toFormattedCurrencyString(8, transactionData.amount.currencySymbol) ?: "0" - - private fun calculateDate(): String { - val calendarTx = transactionData.date.guard { return "Unknown" } - - val calendarNow = Calendar.getInstance() - val oldDateFormatter = SimpleDateFormat("dd.MM.yyyy") - - val date = if (calendarTx.get(Calendar.YEAR) == calendarNow.get(Calendar.YEAR)) { - val dayOfYearTx = calendarTx.get(Calendar.DAY_OF_YEAR) - val dayOfYearNow = calendarNow.get(Calendar.DAY_OF_YEAR) - when { - dayOfYearNow == dayOfYearTx -> "Today" - dayOfYearNow - dayOfYearTx == 1 -> "Tomorrow" - else -> oldDateFormatter.format(calendarTx.time) - } - } else { - oldDateFormatter.format(calendarTx.time) - } - - return date - } - - @Suppress("MagicNumber") - private fun String.reduceAddress(): String = "${substring(0..5)}...${substring(length - 4)}" -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt deleted file mode 100644 index 0363cffefe..0000000000 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/saltPay/rv/TxHistoryAdapter.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.tangem.tap.features.wallet.ui.wallet.saltPay.rv - -import android.graphics.PorterDuff -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.setDrawable -import com.tangem.tap.common.extensions.show -import com.tangem.tap.features.wallet.models.PendingTransactionType -import com.tangem.wallet.R -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryBinding -import com.tangem.wallet.databinding.ItemSaltPayTxHistoryDateBinding - -/** -[REDACTED_AUTHOR] - */ -internal class TxHistoryAdapter : ListAdapter(DiffUtilCallback) { - - override fun getItemId(position: Int): Long = if (currentList.isEmpty()) { - 0L - } else { - currentList[position].itemId - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseTxHistoryVH { - val inflater = LayoutInflater.from(parent.context) - - return when (viewType) { - 0 -> { - val layout = ItemSaltPayTxHistoryDateBinding.inflate(inflater, parent, false) - DateItemVH(layout) - } - 1 -> { - val layout = ItemSaltPayTxHistoryBinding.inflate(inflater, parent, false) - TransactionItemVH(layout) - } - else -> throw UnsupportedOperationException() - } - } - - override fun getItemViewType(position: Int): Int = currentList[position].viewType - - override fun onBindViewHolder(holder: BaseTxHistoryVH, position: Int) { - holder.bind(currentList[position]) - } - - private object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areContentsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem - override fun areItemsTheSame(oldItem: HistoryItemData, newItem: HistoryItemData) = oldItem == newItem - } -} - -internal abstract class BaseTxHistoryVH(layout: View) : RecyclerView.ViewHolder(layout) { - abstract fun bind(data: HistoryItemData) -} - -private class DateItemVH( - private val binding: ItemSaltPayTxHistoryDateBinding, -) : BaseTxHistoryVH(binding.root) { - - override fun bind(data: HistoryItemData) = with(binding) { - val vhData = (data as? HistoryItemData.Date)?.date ?: return - - tvTitleDate.text = vhData - } -} - -private class TransactionItemVH( - private val binding: ItemSaltPayTxHistoryBinding, -) : BaseTxHistoryVH(binding.root) { - - override fun bind(data: HistoryItemData) { - val vhData = (data as? HistoryItemData.TransactionData)?.data ?: return - setupImage(vhData) - setupTitle(vhData) - setupSubtitle(vhData) - setupBalance(vhData) - } - - private fun setupImage(data: HistoryTransactionData) = with(binding) { - val drawable = when (data.transactionType) { - PendingTransactionType.Incoming -> R.drawable.ic_tx_incoming - PendingTransactionType.Outgoing -> R.drawable.ic_tx_outgoing - PendingTransactionType.Unknown -> null - } - drawable?.let { imvTx.setDrawable(it) } - - if (data.isInProgress) { - imvTx.setColorFilter(imvTx.getColor(R.color.icon_attention), PorterDuff.Mode.SRC_ATOP) - } else { - imvTx.clearColorFilter() - } - } - - private fun setupTitle(data: HistoryTransactionData) = with(binding) { - tvTxHash.text = data.address - } - - private fun setupSubtitle(data: HistoryTransactionData) = with(binding) { - tvTxStatus.show(data.isInProgress) - tvTxTime.show(!data.isInProgress) - tvTxTime.text = data.time - } - - private fun setupBalance(data: HistoryTransactionData) = with(binding) { - tvTxAmountSign.text = data.txSign - tvTxAmountValue.text = data.amountValue - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index 077c90ca2c..3528a61c7a 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -22,7 +22,6 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull @@ -72,7 +71,7 @@ internal class WalletSelectorMiddleware { refreshUserWalletsAmounts() } is WalletSelectorAction.ClearUserWallets -> { - disableUserWalletsSaving() + clearUserWalletsAndCloseError() } is WalletSelectorAction.AddWallet.Success, is WalletSelectorAction.AddWallet.Error, @@ -156,16 +155,8 @@ internal class WalletSelectorMiddleware { onFailure = { error -> // Rollback policy if card scanning was failed tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) - when { - error is TangemSdkError.ExceptionError && error.cause is SaltPayActivationError -> { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - } - else -> { - Timber.e(error, "Unable to scan card") - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - } + Timber.e(error, "Unable to scan card") + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) }, ) } @@ -305,12 +296,8 @@ internal class WalletSelectorMiddleware { } } - private fun disableUserWalletsSaving() = scope.launch { + private fun clearUserWalletsAndCloseError() = scope.launch { clearUserWallets() - .map { - preferencesStorage.shouldSaveUserWallets = false - preferencesStorage.shouldSaveAccessCodes = false - } .doOnSuccess { store.dispatchWithMain(WalletSelectorAction.CloseError) popBackToHome() diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 5a947b1cbd..c6339f9cd8 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -19,11 +19,7 @@ import com.tangem.tap.features.walletSelector.ui.model.WarningModel import com.tangem.tap.store import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.* import org.rekotlin.StoreSubscriber internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber { @@ -200,8 +196,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber WarningModel.BiometricsDisabledWarning( - onConfirm = this::clearUserWallets, - onDismiss = this::dismissWarningDialog, + onDismiss = this::clearUserWallets, ) else -> currentDialog } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt index b3b50288d2..61e8bd9000 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt @@ -17,9 +17,10 @@ internal fun BiometricsDisabledWarningContent(warning: WarningModel.BiometricsDi title = stringResource(id = R.string.common_warning), message = stringResource(id = R.string.biometric_unavailable_warning), onDismissDialog = warning.onDismiss, + isDismissable = false, confirmButton = DialogButton( title = stringResource(id = R.string.common_ok), - onClick = warning.onConfirm, + onClick = warning.onDismiss, ), ) } @@ -30,7 +31,6 @@ private fun BiometricsDisabledWarningContentSample(modifier: Modifier = Modifier Column(modifier = modifier) { BiometricsDisabledWarningContent( warning = WarningModel.BiometricsDisabledWarning( - onConfirm = {}, onDismiss = {}, ), ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 71d7f6a253..f0dd6b139b 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -3,14 +3,7 @@ package com.tangem.tap.features.walletSelector.ui.components import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.Icon @@ -28,7 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.SecondaryButtonIconRight +import com.tangem.core.ui.components.SecondaryButtonIconEnd import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.res.TangemTheme @@ -191,11 +184,11 @@ private fun Footer( onClick = onUnlockClick, ) } - SecondaryButtonIconRight( + SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.user_wallet_list_add_button), showProgress = showAddCardProgress, - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = onAddCardClick, ) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt index b458313c35..87039dc113 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt @@ -24,7 +24,6 @@ internal sealed interface WarningModel : DialogModel { ) : WarningModel data class BiometricsDisabledWarning( - val onConfirm: () -> Unit, val onDismiss: () -> Unit, ) : WarningModel } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index a7dc158068..9955bf6d6d 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.doOnFailure import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.common.flatMap -import com.tangem.common.map import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -20,7 +19,6 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable -import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError import com.tangem.tap.features.signin.redux.SignInAction import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -64,16 +62,12 @@ internal class WelcomeMiddleware { private fun disableUserWalletsSaving() = scope.launch { userWalletsListManager.clear() + .flatMap { walletStoresManager.clear() } .flatMap { tangemSdkManager.clearSavedUserCodes() } - .map { - preferencesStorage.shouldSaveUserWallets = false - preferencesStorage.shouldSaveAccessCodes = false - } .doOnFailure { e -> Timber.e(e, "Unable to clear user wallets") } .doOnResult { - // !!! Workaround !!! store.dispatchWithMain(WelcomeAction.CloseError) store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home)) } @@ -116,9 +110,10 @@ internal class WelcomeMiddleware { } private fun handleInitialIntent(intent: Intent?) { - val isBackgroundScanWasHandled = intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) + val isBackgroundScanNotHandled = !intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) + val hasNotIncompletedBackup = !backupService.hasIncompletedBackup - if (!isBackgroundScanWasHandled) { + if (isBackgroundScanNotHandled && hasNotIncompletedBackup) { store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) } } @@ -133,8 +128,8 @@ internal class WelcomeMiddleware { scope.launch { onCardScanned(scanResponse) } }, onFailure = { - when { - it is TangemSdkError.ExceptionError && it.cause is SaltPayActivationError -> { + when (it) { + is TangemSdkError.ExceptionError -> { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) } else -> { diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index ad39cbe5ae..fd40c5038b 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -72,8 +72,7 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber { onDismiss = this::dismissWarning, ) is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning( - onConfirm = this::clearUserWallets, - onDismiss = this::dismissWarning, + onDismiss = this::clearUserWallets, ) else -> null } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt index d8a78e246f..ca9a8d5769 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt @@ -48,9 +48,10 @@ internal fun WarningDialog(warning: WarningModel?) { title = stringResource(id = R.string.common_warning), message = stringResource(id = R.string.biometric_unavailable_warning), onDismissDialog = warning.onDismiss, + isDismissable = false, confirmButton = DialogButton( title = stringResource(id = R.string.common_ok), - onClick = warning.onConfirm, + onClick = warning.onDismiss, ), ) } @@ -140,7 +141,7 @@ private fun KeyInvalidatedWarningPreview_Dark() { @Composable private fun BiometricDisabledWarningSample(modifier: Modifier = Modifier) { Column(modifier = modifier) { - WarningDialog(warning = WarningModel.BiometricsDisabledWarning(onConfirm = {}, onDismiss = {})) + WarningDialog(warning = WarningModel.BiometricsDisabledWarning(onDismiss = {})) } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index 373d370eda..cc83e0d893 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -1,11 +1,7 @@ package com.tangem.tap.features.welcome.ui.components import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -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.layout.* import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -15,12 +11,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.PrimaryButtonIconRight -import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.components.SpacerH32 -import com.tangem.core.ui.components.SpacerHMax +import com.tangem.core.ui.components.* import com.tangem.core.ui.res.TangemTheme import com.tangem.wallet.R @@ -76,13 +67,13 @@ internal fun WelcomeScreenContent( onClick = onUnlockClick, ) SpacerH12() - PrimaryButtonIconRight( + PrimaryButtonIconEnd( modifier = Modifier .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxWidth(), text = stringResource(R.string.welcome_unlock_card), showProgress = showScanCardProgress, - icon = painterResource(id = R.drawable.ic_tangem_24), + iconResId = R.drawable.ic_tangem_24, onClick = onScanCardClick, ) SpacerH16() diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt index 55789fe91a..f8fb28135c 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt @@ -11,7 +11,6 @@ internal sealed interface WarningModel { ) : WarningModel data class BiometricsDisabledWarning( - val onConfirm: () -> Unit, val onDismiss: () -> Unit, ) : WarningModel } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt index 180f96317b..07804272ed 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt @@ -1,36 +1,26 @@ package com.tangem.tap.network.exchangeServices import com.tangem.blockchain.common.Blockchain -import com.tangem.domain.models.scan.ProductType import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService -import com.tangem.tap.network.exchangeServices.utorg.UtorgExchangeService import com.tangem.tap.scope import kotlinx.coroutines.launch /** [REDACTED_AUTHOR] * Temporary wrapper for the buy services. Service switches based on selected product type. - * Just now - UtorgService used only for the SaltPay cards */ internal class BuyExchangeService( - private val productTypeProvider: () -> ProductType?, private val mercuryoService: MercuryoService, - private val utorgService: UtorgExchangeService, ) : ExchangeService, ExchangeUrlBuilder { init { scope.launch { mercuryoService.update() - utorgService.update() } } - private val currentService: ExchangeService - get() = when (productTypeProvider.invoke()) { - ProductType.SaltPay -> utorgService - else -> mercuryoService - } + private val currentService: ExchangeService = mercuryoService override suspend fun update() { currentService.update() diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt deleted file mode 100644 index 0aac5a8b43..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgEnvironment.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg - -import android.net.Uri -import com.tangem.datasource.api.common.createRetrofitInstance -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi -import com.tangem.tap.network.exchangeServices.utorg.mock.MockUtorgApi -import okhttp3.Interceptor -import okhttp3.Response -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -data class UtorgEnvironment( - val baseUri: Uri, - val sidValue: String, - val apiVersion: String, - val utorgApi: UtorgApi, - val successUrl: String = "https://success.tangem.com", -) { - companion object { - private val PROD_BASE_URL = Uri.parse("https://app.utorg.pro") - private const val PROD_VERSION = "v1" - - private val STAGE_BASE_URL = Uri.parse("https://app-stage.utorg.pro") - private const val STAGE_VERSION = "v1" - - fun prod(authProvider: UtorgAuthProvider, apiVersion: String = PROD_VERSION): UtorgEnvironment = - UtorgEnvironment( - baseUri = PROD_BASE_URL, - sidValue = authProvider.sidValue, - apiVersion = apiVersion, - utorgApi = createApi(PROD_BASE_URL, authProvider, false), - ) - - fun stage( - authProvider: UtorgAuthProvider, - logEnabled: Boolean, - apiVersion: String = STAGE_VERSION, - ): UtorgEnvironment = UtorgEnvironment( - baseUri = STAGE_BASE_URL, - sidValue = authProvider.sidValue, - apiVersion = apiVersion, - utorgApi = createApi(STAGE_BASE_URL, authProvider, logEnabled), - ) - - fun mock(): UtorgEnvironment = UtorgEnvironment( - baseUri = Uri.EMPTY, - sidValue = "", - apiVersion = "", - utorgApi = MockUtorgApi(), - ) - - private fun createApi(baseUri: Uri, authProvider: UtorgAuthProvider, logEnabled: Boolean): UtorgApi { - return createRetrofitInstance( - baseUrl = "$baseUri/", - interceptors = listOf(UtorgAuthHeaderInterceptor(authProvider)), - logEnabled = logEnabled, - ).create(UtorgApi::class.java) - } - } -} - -data class UtorgAuthProvider( - val sidValue: String, - val headerSidKey: String = "X-AUTH-SID", - val headerNonceKey: String = "X-AUTH-NONCE", -) { - val clientNonce: String - get() = UUID.randomUUID().toString() -} - -private class UtorgAuthHeaderInterceptor( - private val authProvider: UtorgAuthProvider, -) : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - .newBuilder() - .addHeader(authProvider.headerSidKey, authProvider.sidValue) - .addHeader(authProvider.headerNonceKey, authProvider.clientNonce) - .build() - - return chain.proceed(request) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt deleted file mode 100644 index 820751a14b..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/UtorgExchangeService.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg - -import android.net.Uri -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyData -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyType -import com.tangem.utils.converter.Converter - -/** -[REDACTED_AUTHOR] - */ -class UtorgExchangeService( - private val environment: UtorgEnvironment, - private val currencyConverter: Converter = ChainToBlockchainConverter(), -) : ExchangeService { - - private val api = environment.utorgApi - - private val utorgCurrencies = mutableListOf() - - override fun featureIsSwitchedOn(): Boolean = true - - override suspend fun update() { - when (val result = performRequest { api.getCurrency(environment.apiVersion) }) { - is Result.Success -> { - if (!result.data.isSuccess()) return - - val utorgCryptos = result.data.toSuccess().data.filter { - it.enabled && it.type == UtorgCurrencyType.CRYPTO - } - utorgCurrencies.clear() - utorgCurrencies.addAll(utorgCryptos) - - performRequest { - api.setSuccessUrl(environment.apiVersion, RequestSuccessUrl(environment.successUrl)) - } - } - is Result.Failure -> { - utorgCurrencies.clear() - } - } - } - - override fun isBuyAllowed(): Boolean = true - - override fun isSellAllowed(): Boolean = false - - override fun availableForBuy(currency: Currency): Boolean { - if (!isBuyAllowed()) return false - - val foundUtorgCurrency = utorgCurrencies.firstOrNull { utorgCurrency -> - val utorgBlockchain = currencyConverter.convert(utorgCurrency) ?: return@firstOrNull false - - val isSameBlockchain = utorgBlockchain == currency.blockchain - val isSameSymbol = utorgCurrency.symbol.lowercase() == currency.currencySymbol.lowercase() - isSameBlockchain && isSameSymbol - } - - return foundUtorgCurrency != null - } - - override fun availableForSell(currency: Currency): Boolean = false - - override fun getUrl( - action: CurrencyExchangeManager.Action, - blockchain: Blockchain, - cryptoCurrencyName: CryptoCurrencyName, - fiatCurrencyName: String, - walletAddress: String, - ): String { - if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() - - // https://app-stage.utorg.pro/direct/testSID/mvmeaWyWiuVYdZdySgofAt6CmKhJbRhaWA/?¤cy=BTC - val builder = Uri.Builder() - .scheme(environment.baseUri.scheme) - .authority(environment.baseUri.authority) - .appendPath("direct") - .appendPath(environment.sidValue) - .appendPath(walletAddress) - .appendQueryParameter("currency", cryptoCurrencyName) - // if we set the paymentCurrency, then the Utorg widget didn't work - // .appendQueryParameter("paymentCurrency", "USD") - - val url = builder.build().toString() - return url - } - - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null -} - -private class ChainToBlockchainConverter : Converter { - override fun convert(value: UtorgCurrencyData): Blockchain? { - return when (value.chain?.uppercase()) { - "ARBITRUM" -> Blockchain.Arbitrum - "AVALANCHE" -> Blockchain.Avalanche - "BINANCE_SMART_CHAIN" -> Blockchain.BSC - "BITCOIN" -> Blockchain.Bitcoin - "BNB" -> Blockchain.Binance - "ETHEREUM" -> Blockchain.Ethereum - "GNOSIS" -> Blockchain.Gnosis - "POLYGON" -> Blockchain.Polygon - "RIPPLE" -> Blockchain.XRP - "RSK" -> Blockchain.RSK - "SOLANA" -> Blockchain.Solana - else -> null - // "APTOS", "ATOM", "NEAR", "ZKSYNC", "VECHAIN", "VELAS", "VELAS_EVM" -> null - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt deleted file mode 100644 index b18618e38b..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/UtorgApi.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.api - -import com.squareup.moshi.Json -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse -import retrofit2.http.Body -import retrofit2.http.POST -import retrofit2.http.Path - -/** -[REDACTED_AUTHOR] - */ -interface UtorgApi { - - @POST("api/merchant/{apiVersion}/settings/currency") - suspend fun getCurrency(@Path("apiVersion") apiVersion: String): UtorgCurrencyResponse - - @POST("api/merchant/{apiVersion}/settings/successUrl") - suspend fun setSuccessUrl(@Path("apiVersion") apiVersion: String, @Body request: RequestSuccessUrl) -} - -data class RequestSuccessUrl( - val url: String, -) - -interface UtorgResponse { - val success: Boolean - val timestamp: Long - val data: Data? - val error: UtorgErrorResponse? - - fun isSuccess(): Boolean = success && data != null && error == null - - @Throws(NullPointerException::class) - fun toSuccess(): UtorgSuccessResponse { - val internalTimestamp = this.timestamp - val internalData = this.data - - return object : UtorgSuccessResponse { - override val timestamp: Long = internalTimestamp - override val data: Data = internalData!! - } - } - - @Throws(NullPointerException::class) - fun toError(): UtorgErrorResponse = error!! -} - -interface UtorgSuccessResponse { - val timestamp: Long - val data: T -} - -data class UtorgErrorResponse( - @Json(name = "message") val message: String?, - @Json(name = "type") val type: UtorgErrorType, -) - -enum class UtorgErrorType { - UNAUTHORIZED, - UNKNOWN_ERROR, - BAD_REQUEST, -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt deleted file mode 100644 index 5df98687d8..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/api/model/UtorgCurrencyResponse.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.api.model - -import com.squareup.moshi.Json -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgResponse - -class UtorgCurrencyResponse( - @Json(name = "success") override val success: Boolean, - @Json(name = "timestamp") override val timestamp: Long, - @Json(name = "data") override val data: List?, - @Json(name = "error") override val error: UtorgErrorResponse?, -) : UtorgResponse> - -data class UtorgCurrencyData( - @Json(name = "currency") val currency: String, - @Json(name = "symbol") val symbol: String, - @Json(name = "enabled") val enabled: Boolean, - @Json(name = "type") val type: UtorgCurrencyType, - @Json(name = "caption") val caption: String?, - @Json(name = "chain") val chain: String?, -) - -enum class UtorgCurrencyType { - FIAT, CRYPTO -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt deleted file mode 100644 index e50b8b8833..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgApi.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock - -import com.tangem.common.json.MoshiJsonConverter -import com.tangem.tap.network.exchangeServices.utorg.api.RequestSuccessUrl -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgApi -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorResponse -import com.tangem.tap.network.exchangeServices.utorg.api.UtorgErrorType -import com.tangem.tap.network.exchangeServices.utorg.api.model.UtorgCurrencyResponse -import kotlinx.coroutines.delay - -/** -[REDACTED_AUTHOR] - */ -class MockUtorgApi : UtorgApi { - - var nextDelay = 500L - - var nextResponseType = ResponseType.Success - var nextErrorType = UtorgErrorType.UNKNOWN_ERROR - - override suspend fun getCurrency(apiVersion: String): UtorgCurrencyResponse { - delay(nextDelay) - return UtorgCurrencyResponse( - success = createIsSuccess(), - timestamp = createTime(), - data = createData(MockUtorgSuccessDataResponse.GetCurrency), - error = createError(), - ) - } - - override suspend fun setSuccessUrl(apiVersion: String, request: RequestSuccessUrl) { - delay(nextDelay) - } - - private fun createIsSuccess(): Boolean = when (nextResponseType) { - ResponseType.Success -> true - ResponseType.Error -> false - } - - private fun createTime(): Long = System.currentTimeMillis() - - private inline fun createData(response: MockUtorgSuccessDataResponse): T? = when (nextResponseType) { - ResponseType.Success -> MoshiJsonConverter.INSTANCE.fromJson(response.json)!! - ResponseType.Error -> null - } - - private fun createError(): UtorgErrorResponse? = when (nextResponseType) { - ResponseType.Success -> null - ResponseType.Error -> UtorgErrorResponse( - message = "Optional message", - type = nextErrorType, - ) - } - - enum class ResponseType { - Success, Error - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt deleted file mode 100644 index 959e65a4d5..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/MockUtorgSuccessDataResponse.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock - -import com.tangem.tap.network.exchangeServices.utorg.mock.json.getCurrencyFull - -/** -[REDACTED_AUTHOR] - */ -sealed class MockUtorgSuccessDataResponse( - val json: String, -) { - object GetCurrency : MockUtorgSuccessDataResponse(getCurrencyFull) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt deleted file mode 100644 index 3d50462305..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/utorg/mock/json/CurrencyResponseJson.kt +++ /dev/null @@ -1,1025 +0,0 @@ -package com.tangem.tap.network.exchangeServices.utorg.mock.json - -/** -[REDACTED_AUTHOR] - */ -internal val getCurrencyFull = """ -[ - { - "currency": "APT", - "symbol": "APT", - "chain": "APTOS", - "display": "APT", - "caption": "Aptos", - "explorerTx": "https://aptoscan.com/version/", - "explorerAddr": "https://aptoscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.8492, - "depositMax": 739.6673, - "withdrawalMin": 1.8492, - "withdrawalMax": 739.6673, - "addressValidator": "APT", - "precision": 4, - "allowTag": false - }, - { - "currency": "ATOM", - "symbol": "ATOM", - "chain": "ATOM", - "display": "ATOM", - "caption": "Cosmos", - "explorerTx": "https://atomscan.com/transactions/", - "explorerAddr": "https://atomscan.com/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.8020, - "depositMax": 720.7911, - "withdrawalMin": 1.8020, - "withdrawalMax": 720.7911, - "addressValidator": "COSMOS", - "precision": 4, - "allowTag": true, - "udKey": "crypto.ATOM.address" - }, - { - "currency": "AUD", - "symbol": "AUD", - "display": "AUD", - "type": "FIAT", - "enabled": true, - "depositMin": 43.73, - "depositMax": 15013.68, - "withdrawalMin": 43.73, - "withdrawalMax": 15013.68, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "AVAXAVA", - "symbol": "AVAX", - "chain": "AVALANCHE", - "display": "Avax", - "caption": "Avalanche", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.337428, - "depositMax": 534.971102, - "withdrawalMin": 1.337428, - "withdrawalMax": 534.971102, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false, - "udKey": "crypto.AVAX.address" - }, - { - "currency": "BNB", - "symbol": "BNB", - "chain": "BNB", - "display": "BNB", - "caption": "BEP2", - "explorerTx": "https://explorer.binance.org/tx/", - "explorerAddr": "https://explorer.binance.org/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.082738, - "depositMax": 33.095138, - "withdrawalMin": 0.082738, - "withdrawalMax": 33.095138, - "addressValidator": "BNB", - "precision": 6, - "allowTag": true, - "udKey": "crypto.BNB.address" - }, - { - "currency": "BNBBSC", - "symbol": "BNB", - "chain": "BINANCE_SMART_CHAIN", - "display": "BNB", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.0828, - "depositMax": 33.0951, - "withdrawalMin": 0.0828, - "withdrawalMax": 33.0951, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "BRL", - "symbol": "BRL", - "display": "BRL", - "type": "FIAT", - "enabled": true, - "depositMin": 158.08, - "depositMax": 54293.21, - "withdrawalMin": 158.08, - "withdrawalMax": 54293.21, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "BTC", - "symbol": "BTC", - "chain": "BITCOIN", - "display": "BTC", - "caption": "", - "explorerTx": "https://blockstream.info/tx/", - "explorerAddr": "https://blockstream.info/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 528764.805415, - "depositMax": 211505922.165820, - "withdrawalMin": 528764.805415, - "withdrawalMax": 211505922.165820, - "addressValidator": "BTC", - "precision": 6, - "allowTag": false, - "udKey": "crypto.BTC.address" - }, - { - "currency": "BUSDAVA", - "symbol": "BUSD", - "chain": "AVALANCHE", - "display": "BUSD", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "BUSDBSC", - "symbol": "BUSD", - "chain": "BINANCE_SMART_CHAIN", - "display": "BUSD", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6414, - "depositMax": 10656.5563, - "withdrawalMin": 26.6414, - "withdrawalMax": 10656.5563, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.BUSD.version.BEP20.address" - }, - { - "currency": "BUSDETH", - "symbol": "BUSD", - "chain": "ETHEREUM", - "display": "BUSD", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6414, - "depositMax": 10656.5563, - "withdrawalMin": 26.6414, - "withdrawalMax": 10656.5563, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.BUSD.version.ERC20.address" - }, - { - "currency": "CAD", - "symbol": "CAD", - "display": "CAD", - "type": "FIAT", - "enabled": true, - "depositMin": 40.85, - "depositMax": 14024.33, - "withdrawalMin": 40.85, - "withdrawalMax": 14024.33, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "CZK", - "symbol": "CZK", - "display": "CZK", - "type": "FIAT", - "enabled": true, - "depositMin": 672.71, - "depositMax": 231068.12, - "withdrawalMin": 672.71, - "withdrawalMax": 231068.12, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "DAIBSC", - "symbol": "DAI", - "chain": "BINANCE_SMART_CHAIN", - "display": "DAI", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6462, - "depositMax": 10658.4634, - "withdrawalMin": 26.6462, - "withdrawalMax": 10658.4634, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DAIEAVA", - "symbol": "DAIE", - "chain": "AVALANCHE", - "display": "DAI.e", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6052, - "depositMax": 10642.0513, - "withdrawalMin": 26.6052, - "withdrawalMax": 10642.0513, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DAIETH", - "symbol": "DAI", - "chain": "ETHEREUM", - "display": "DAI", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6436, - "depositMax": 10657.4090, - "withdrawalMin": 26.6436, - "withdrawalMax": 10657.4090, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.DAI.address" - }, - { - "currency": "DAIPOL", - "symbol": "DAI", - "chain": "POLYGON", - "display": "DAI", - "caption": "on Polygon", - "explorerTx": "https://polygonscan.com/tx/", - "explorerAddr": "https://polygonscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6436, - "depositMax": 10657.4090, - "withdrawalMin": 26.6436, - "withdrawalMax": 10657.4090, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "DKK", - "symbol": "DKK", - "display": "DKK", - "type": "FIAT", - "enabled": true, - "depositMin": 210.64, - "depositMax": 72348.14, - "withdrawalMin": 210.64, - "withdrawalMax": 72348.14, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "ETH", - "symbol": "ETH", - "chain": "ETHEREUM", - "display": "ETH", - "caption": "", - "explorerTx": "https://ropsten.etherscan.io/tx/", - "explorerAddr": "https://ropsten.etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 37688.767964, - "depositMax": 15075507.185288, - "withdrawalMin": 37688.767964, - "withdrawalMax": 15075507.185288, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false, - "udKey": "crypto.ETH.address" - }, - { - "currency": "EUR", - "symbol": "EUR", - "display": "EUR", - "caption": "", - "type": "FIAT", - "enabled": true, - "depositMin": 27.24, - "depositMax": 9349.53, - "withdrawalMin": 27.24, - "withdrawalMax": 9349.53, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "GBP", - "symbol": "GBP", - "display": "GBP", - "type": "FIAT", - "enabled": true, - "depositMin": 25.14, - "depositMax": 8630.60, - "withdrawalMin": 25.14, - "withdrawalMax": 8630.60, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "GMTBSC", - "symbol": "GMT", - "chain": "BINANCE_SMART_CHAIN", - "display": "GMT", - "caption": "BEP20", - "explorerTx": "https://bscscan.com/tx/", - "explorerAddr": "https://bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 53.9899, - "depositMax": 21595.9496, - "withdrawalMin": 53.9899, - "withdrawalMax": 21595.9496, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "GNOETH", - "symbol": "GNO", - "chain": "ETHEREUM", - "display": "GNO", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 0.2250, - "depositMax": 89.9938, - "withdrawalMin": 0.2250, - "withdrawalMax": 89.9938, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.GNO.address" - }, - { - "currency": "HAIBSC", - "symbol": "HAI", - "chain": "BINANCE_SMART_CHAIN", - "display": "HAI", - "caption": "on BSC", - "explorerTx": "https://bscscan.com/tx/", - "explorerAddr": "https://bscscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 937.548948, - "depositMax": 375019.579098, - "withdrawalMin": 937.548948, - "withdrawalMax": 375019.579098, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false - }, - { - "currency": "HAIVET", - "symbol": "HAI", - "chain": "VECHAIN", - "display": "HAI", - "caption": "VET", - "explorerTx": "https://vechainstats.com/transaction/", - "explorerAddr": "https://vechainstats.com/account/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 949.3974, - "depositMax": 379758.9569, - "withdrawalMin": 949.3974, - "withdrawalMax": 379758.9569, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "IDR", - "symbol": "IDR", - "display": "IDR", - "type": "FIAT", - "enabled": true, - "depositMin": 459558, - "depositMax": 157855988, - "withdrawalMin": 459558, - "withdrawalMax": 157855988, - "addressValidator": "LUHN", - "precision": 0, - "allowTag": false - }, - { - "currency": "INR", - "symbol": "INR", - "display": "INR", - "type": "FIAT", - "enabled": true, - "depositMin": 2511.35, - "depositMax": 862635.95, - "withdrawalMin": 2511.35, - "withdrawalMax": 862635.95, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "JPY", - "symbol": "JPY", - "display": "JPY", - "type": "FIAT", - "enabled": true, - "depositMin": 3992, - "depositMax": 1370884, - "withdrawalMin": 3992, - "withdrawalMax": 1370884, - "addressValidator": "LUHN", - "precision": 0, - "allowTag": false - }, - { - "currency": "KZT", - "symbol": "KZT", - "display": "KZT", - "type": "FIAT", - "enabled": true, - "depositMin": 13843.18, - "depositMax": 4755080.66, - "withdrawalMin": 13843.18, - "withdrawalMax": 4755080.66, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "MH3ETH", - "symbol": "MH3", - "chain": "ETHEREUM", - "display": "NFT", - "caption": "Meta History", - "explorerTx": "https://rinkeby.etherscan.io/tx/", - "explorerAddr": "https://rinkeby.etherscan.io/address/", - "type": "CRYPTO", - "enabled": false, - "depositMin": 0, - "depositMax": 0, - "withdrawalMin": 0, - "withdrawalMax": 0, - "addressValidator": "ETH", - "precision": 0, - "allowTag": false, - "nftContract": "0xf0192b97135abaa218bcff255b8f4b22171a7590" - }, - { - "currency": "MXN", - "symbol": "MXN", - "display": "MXN", - "type": "FIAT", - "enabled": true, - "depositMin": 574.83, - "depositMax": 197445.63, - "withdrawalMin": 574.83, - "withdrawalMax": 197445.63, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "MYR", - "symbol": "MYR", - "display": "MYR", - "type": "FIAT", - "enabled": true, - "depositMin": 131.04, - "depositMax": 45006.86, - "withdrawalMin": 131.04, - "withdrawalMax": 45006.86, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "NEAR", - "symbol": "NEAR", - "chain": "NEAR", - "display": "NEAR", - "caption": "", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 10.560163, - "depositMax": 4224.064906, - "withdrawalMin": 10.560163, - "withdrawalMax": 4224.064906, - "addressValidator": "NEAR", - "precision": 6, - "allowTag": false, - "udKey": "crypto.NEAR.address" - }, - { - "currency": "NOK", - "symbol": "NOK", - "display": "NOK", - "type": "FIAT", - "enabled": true, - "depositMin": 312.23, - "depositMax": 107245.60, - "withdrawalMin": 312.23, - "withdrawalMax": 107245.60, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "NXUSDAVA", - "symbol": "NXUSD", - "chain": "AVALANCHE", - "display": "NXUSD", - "caption": "Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.8766, - "depositMax": 10750.6217, - "withdrawalMin": 26.8766, - "withdrawalMax": 10750.6217, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "NZD", - "symbol": "NZD", - "display": "NZD", - "type": "FIAT", - "enabled": true, - "depositMin": 47.90, - "depositMax": 16448.49, - "withdrawalMin": 47.90, - "withdrawalMax": 16448.49, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "PLN", - "symbol": "PLN", - "display": "PLN", - "type": "FIAT", - "enabled": true, - "depositMin": 134.06, - "depositMax": 46047.85, - "withdrawalMin": 134.06, - "withdrawalMax": 46047.85, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "RIF", - "symbol": "RIF", - "chain": "RSK", - "display": "RIF", - "caption": "RSK", - "explorerTx": "https://explorer.rsk.co/tx/", - "explorerAddr": "https://explorer.rsk.co/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 429.65, - "depositMax": 171859.31, - "withdrawalMin": 429.65, - "withdrawalMax": 171859.31, - "addressValidator": "ETH", - "precision": 2, - "allowTag": false, - "udKey": "crypto.RIF.address" - }, - { - "currency": "RUB", - "symbol": "RUB", - "display": "RUB", - "type": "FIAT", - "enabled": true, - "depositMin": 2217.40, - "depositMax": 761666.68, - "withdrawalMin": 2217.40, - "withdrawalMax": 761666.68, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "SOL", - "symbol": "SOL", - "chain": "SOLANA", - "display": "SOL", - "caption": "Solana", - "explorerTx": "https://explorer.solana.com/tx/", - "explorerAddr": "https://explorer.solana.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1.155094, - "depositMax": 462.037519, - "withdrawalMin": 1.155094, - "withdrawalMax": 462.037519, - "addressValidator": "SOLANA", - "precision": 6, - "allowTag": false, - "udKey": "crypto.SOL.address" - }, - { - "currency": "TRY", - "symbol": "TRY", - "display": "TRY", - "type": "FIAT", - "enabled": true, - "depositMin": 572.28, - "depositMax": 196571.08, - "withdrawalMin": 572.28, - "withdrawalMax": 196571.08, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "TWTBNB", - "symbol": "TWT", - "chain": "BNB", - "display": "TWT", - "caption": "BEP2", - "explorerTx": "https://explorer.binance.org/tx/", - "explorerAddr": "https://explorer.binance.org/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 17.4457, - "depositMax": 6978.2637, - "withdrawalMin": 17.4457, - "withdrawalMax": 6978.2637, - "addressValidator": "BNB", - "precision": 4, - "allowTag": true, - "udKey": "crypto.TWT.address" - }, - { - "currency": "UAH", - "symbol": "UAH", - "display": "UAH", - "caption": "", - "type": "FIAT", - "enabled": true, - "depositMin": 1118.00, - "depositMax": 14005.59, - "withdrawalMin": 1118.00, - "withdrawalMax": 14005.59, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "USD", - "symbol": "USD", - "display": "USD", - "type": "FIAT", - "enabled": true, - "depositMin": 30.57, - "depositMax": 10382.96, - "withdrawalMin": 30.57, - "withdrawalMax": 10382.96, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - }, - { - "currency": "USDCARB", - "symbol": "USDC", - "chain": "ARBITRUM", - "display": "USDC", - "caption": "on arbitrum", - "explorerTx": "https://arbiscan.io/tx/", - "explorerAddr": "https://arbiscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCBSC", - "symbol": "USDC", - "chain": "BINANCE_SMART_CHAIN", - "display": "USDC", - "caption": "BEP20", - "explorerTx": "https://www.bscscan.com/tx/", - "explorerAddr": "https://www.bscscan.com/address/", - "type": "CRYPTO", - "enabled": false, - "depositMin": 0, - "depositMax": 0, - "withdrawalMin": 0, - "withdrawalMax": 0, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCEAVA", - "symbol": "USDCE", - "chain": "AVALANCHE", - "display": "USDC.e", - "caption": "on Avalanche", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6078, - "depositMax": 10643.1155, - "withdrawalMin": 26.6078, - "withdrawalMax": 10643.1155, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDCETH", - "symbol": "USDC", - "chain": "ETHEREUM", - "display": "USDC", - "caption": "ERC20", - "explorerTx": "https://etherscan.io/tx/", - "explorerAddr": "https://etherscan.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6382, - "depositMax": 10655.2775, - "withdrawalMin": 26.6382, - "withdrawalMax": 10655.2775, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false, - "udKey": "crypto.USDC.address" - }, - { - "currency": "USDCZK", - "symbol": "USDC", - "chain": "ZKSYNC", - "display": "USDC", - "caption": "on zkSync", - "explorerTx": "https://zkscan.io/explorer/transactions/", - "explorerAddr": "https://zkscan.io/explorer/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6078, - "depositMax": 10643.1155, - "withdrawalMin": 26.6078, - "withdrawalMax": 10643.1155, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDTAVA", - "symbol": "USDT", - "chain": "AVALANCHE", - "display": "USDT", - "caption": "on Avalanche", - "explorerTx": "https://snowtrace.io/tx/", - "explorerAddr": "https://snowtrace.io/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6649, - "depositMax": 10665.9328, - "withdrawalMin": 26.6649, - "withdrawalMax": 10665.9328, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDTE", - "symbol": "USDT", - "chain": "ETHEREUM", - "display": "USDT", - "caption": "", - "explorerTx": "", - "explorerAddr": "", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.66483213, - "depositMax": 10665.93285048, - "withdrawalMin": 26.66483213, - "withdrawalMax": 10665.93285048, - "addressValidator": "ETH", - "precision": 8, - "allowTag": false, - "udKey": "crypto.USDT.version.ERC20.address" - }, - { - "currency": "USDTPOL", - "symbol": "USDT", - "chain": "POLYGON", - "display": "USDT", - "caption": "on Polygon", - "explorerTx": "https://polygonscan.com/tx/", - "explorerAddr": "https://polygonscan.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6649, - "depositMax": 10665.9328, - "withdrawalMin": 26.6649, - "withdrawalMax": 10665.9328, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "USDVEL", - "symbol": "USDV", - "chain": "VELAS_EVM", - "display": "USDV", - "caption": "Velas EVM", - "explorerTx": "https://evmexplorer.velas.com/tx/", - "explorerAddr": "https://evmexplorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26611608.908400, - "depositMax": 10644643563.359833, - "withdrawalMin": 26611608.908400, - "withdrawalMax": 10644643563.359833, - "addressValidator": "ETH", - "precision": 6, - "allowTag": false - }, - { - "currency": "USNNEAR", - "symbol": "USN", - "chain": "NEAR", - "display": "USN", - "caption": "Near", - "explorerTx": "https://explorer.near.org/transactions/", - "explorerAddr": "https://explorer.near.org/accounts/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6969, - "depositMax": 10678.7473, - "withdrawalMin": 26.6969, - "withdrawalMax": 10678.7473, - "addressValidator": "NEAR", - "precision": 4, - "allowTag": false - }, - { - "currency": "VLX", - "symbol": "VLX", - "chain": "VELAS", - "display": "VLX", - "caption": "Velas", - "explorerTx": "https://explorer.velas.com/tx/", - "explorerAddr": "https://explorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1021.951187, - "depositMax": 408780.474783, - "withdrawalMin": 1021.951187, - "withdrawalMax": 408780.474783, - "addressValidator": "SOLANA", - "precision": 6, - "allowTag": false, - "udKey": "crypto.VLX.address" - }, - { - "currency": "VLXETH", - "symbol": "VLX", - "chain": "VELAS_EVM", - "display": "VLX", - "caption": "Velas EVM", - "explorerTx": "https://evmexplorer.velas.com/tx/", - "explorerAddr": "https://evmexplorer.velas.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 1021.9512, - "depositMax": 408780.4747, - "withdrawalMin": 1021.9512, - "withdrawalMax": 408780.4747, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "WLKNSOL", - "symbol": "WLKN", - "chain": "SOLANA", - "display": "WLKN", - "caption": "Solana", - "explorerTx": "https://explorer.solana.com/tx/", - "explorerAddr": "https://explorer.solana.com/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 531.89, - "depositMax": 212755.45, - "withdrawalMin": 531.89, - "withdrawalMax": 212755.45, - "addressValidator": "SOLANA", - "precision": 2, - "allowTag": false - }, - { - "currency": "XDAIGNO", - "symbol": "xDAI", - "chain": "GNOSIS", - "display": "xDAI", - "caption": "Gnosis chain", - "explorerTx": "https://blockscout.com/xdai/mainnet/tx/", - "explorerAddr": "https://blockscout.com/xdai/mainnet/address/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 26.6782, - "depositMax": 10671.2684, - "withdrawalMin": 26.6782, - "withdrawalMax": 10671.2684, - "addressValidator": "ETH", - "precision": 4, - "allowTag": false - }, - { - "currency": "XRP", - "symbol": "XRP", - "chain": "RIPPLE", - "display": "XRP", - "caption": "Ripple", - "explorerTx": "https://xrpscan.com/tx/", - "explorerAddr": "https://xrpscan.com/account/", - "type": "CRYPTO", - "enabled": true, - "depositMin": 67.7645, - "depositMax": 27105.7684, - "withdrawalMin": 67.7645, - "withdrawalMax": 27105.7684, - "addressValidator": "XRP", - "precision": 4, - "allowTag": true, - "udKey": "crypto.XRP.address" - }, - { - "currency": "ZAR", - "symbol": "ZAR", - "display": "ZAR", - "type": "FIAT", - "enabled": true, - "depositMin": 538.27, - "depositMax": 184892.03, - "withdrawalMin": 538.27, - "withdrawalMax": 184892.03, - "addressValidator": "LUHN", - "precision": 2, - "allowTag": false - } - ] -""".trimIndent() \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index 24b221cc39..96430194c1 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -20,6 +20,7 @@ import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic +import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.domain.tokens.models.BlockchainNetwork @@ -36,14 +37,11 @@ class TransactionManagerImpl( ) : TransactionManager { override suspend fun sendApproveTransaction( - networkId: String, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: ApproveTxData, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) walletManager.update() val amount = Amount(value = BigDecimal.ZERO, blockchain = blockchain) @@ -51,40 +49,55 @@ class TransactionManagerImpl( walletManager = walletManager, amount = amount, blockchain = blockchain, - feeAmount = feeAmount, - gasLimit = gasLimit, - destinationAddress = destinationAddress, - dataToSign = dataToSign, + feeAmount = txData.feeAmount, + gasLimit = txData.gasLimit, + destinationAddress = txData.destinationAddress, + dataToSign = txData.dataToSign, + successEvent = AnalyticsParam.TxSentFrom.Approve( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + permissionType = analyticsData.permissionType ?: "", + ), ) } override suspend fun sendTransaction( - networkId: String, - amountToSend: BigDecimal, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: SwapTxData, isSwap: Boolean, - currencyToSend: Currency, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) walletManager.update() val amount = if (isSwap) { - createAmountForSwap(amountToSend, currencyToSend, blockchain) + createAmountForSwap(txData.amountToSend, txData.currencyToSend, blockchain) } else { - createAmount(amountToSend, currencyToSend, blockchain) + createAmount(txData.amountToSend, txData.currencyToSend, blockchain) + } + val successEvent = if (isSwap) { + AnalyticsParam.TxSentFrom.Swap( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + ) + } else { + AnalyticsParam.TxSentFrom.Send( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + ) } return sendTransactionInternal( walletManager = walletManager, amount = amount, blockchain = blockchain, - feeAmount = feeAmount, - gasLimit = gasLimit, - destinationAddress = destinationAddress, - dataToSign = dataToSign, + feeAmount = txData.feeAmount, + gasLimit = txData.gasLimit, + destinationAddress = txData.destinationAddress, + dataToSign = txData.dataToSign, + successEvent = successEvent, ) } @@ -97,6 +110,7 @@ class TransactionManagerImpl( gasLimit: Int, destinationAddress: String, dataToSign: String, + successEvent: AnalyticsParam.TxSentFrom, ): SendTxResult { val txData = walletManager.createTransaction( amount = amount, @@ -112,7 +126,10 @@ class TransactionManagerImpl( FirebaseCrashlytics.getInstance().recordException(ex) return SendTxResult.UnknownError(ex) } - return handleSendResult(sendResult) + return handleSendResult( + result = sendResult, + successEvent = successEvent, + ) } override fun getExplorerTransactionLink(networkId: String, txAddress: String): String { @@ -337,10 +354,10 @@ class TransactionManagerImpl( } } - private fun handleSendResult(result: SimpleResult): SendTxResult { + private fun handleSendResult(result: SimpleResult, successEvent: AnalyticsParam.TxSentFrom): SendTxResult { when (result) { is SimpleResult.Success -> { - analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Swap)) + analytics.send(Basic.TransactionSent(sentFrom = successEvent, memoType = MemoType.Null)) return SendTxResult.Success } is SimpleResult.Failure -> { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 99734202b0..5fab5032f5 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -3,6 +3,7 @@ package com.tangem.tap.proxy.redux import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.features.wallet.navigation.WalletRouter +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import org.rekotlin.Action sealed interface DaggerGraphAction : Action { @@ -11,5 +12,6 @@ sealed interface DaggerGraphAction : Action { val testerRouter: TesterRouter, val scanCardUseCase: ScanCardUseCase, val walletRouter: WalletRouter, + val walletConnectInteractor: WalletConnectInteractor, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index a2c7994ce1..bdc38a631c 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -16,6 +16,7 @@ object DaggerGraphReducer { testerRouter = action.testerRouter, scanCardUseCase = action.scanCardUseCase, walletRouter = action.walletRouter, + walletConnectInteractor = action.walletConnectInteractor, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index b59edc1928..a741e4dfd4 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -6,6 +6,9 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.features.wallet.navigation.WalletRouter +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository +import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -17,6 +20,9 @@ data class DaggerGraphState( val scanCardUseCase: ScanCardUseCase? = null, val walletFeatureToggles: WalletFeatureToggles? = null, val walletRouter: WalletRouter? = null, + val walletConnectRepository: WalletConnectRepository? = null, + val walletConnectSessionsRepository: WalletConnectSessionsRepository? = null, + val walletConnectInteractor: WalletConnectInteractor? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/app/src/main/res/color/selector_saltpay_btn.xml b/app/src/main/res/color/selector_saltpay_btn.xml deleted file mode 100644 index 98dd6f0aba..0000000000 --- a/app/src/main/res/color/selector_saltpay_btn.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_saltpay_btn_text.xml b/app/src/main/res/color/selector_saltpay_btn_text.xml deleted file mode 100644 index 28e2f2cfdd..0000000000 --- a/app/src/main/res/color/selector_saltpay_btn_text.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_eth.png b/app/src/main/res/drawable-hdpi/ic_eth.png deleted file mode 100644 index 9db27f814e..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_eth.webp b/app/src/main/res/drawable-hdpi/ic_eth.webp new file mode 100644 index 0000000000..ffe1bc6185 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-hdpi/meet_tangem.png b/app/src/main/res/drawable-hdpi/meet_tangem.png deleted file mode 100644 index 25f2d5a3ba..0000000000 Binary files a/app/src/main/res/drawable-hdpi/meet_tangem.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/meet_tangem.webp b/app/src/main/res/drawable-hdpi/meet_tangem.webp new file mode 100644 index 0000000000..e9b4ef6b19 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/meet_tangem.webp differ diff --git a/app/src/main/res/drawable-hdpi/revolutionary_wallet.png b/app/src/main/res/drawable-hdpi/revolutionary_wallet.png deleted file mode 100644 index f4f384a54d..0000000000 Binary files a/app/src/main/res/drawable-hdpi/revolutionary_wallet.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/revolutionary_wallet.webp b/app/src/main/res/drawable-hdpi/revolutionary_wallet.webp new file mode 100644 index 0000000000..7e0f631f9c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/revolutionary_wallet.webp differ diff --git a/app/src/main/res/drawable-hdpi/wallet_for_everyone.png b/app/src/main/res/drawable-hdpi/wallet_for_everyone.png deleted file mode 100644 index 6eec2f53dd..0000000000 Binary files a/app/src/main/res/drawable-hdpi/wallet_for_everyone.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/wallet_for_everyone.webp b/app/src/main/res/drawable-hdpi/wallet_for_everyone.webp new file mode 100644 index 0000000000..e50874dfbf Binary files /dev/null and b/app/src/main/res/drawable-hdpi/wallet_for_everyone.webp differ diff --git a/app/src/main/res/drawable-ldpi/ic_eth.png b/app/src/main/res/drawable-ldpi/ic_eth.png deleted file mode 100644 index b8cd7b26ab..0000000000 Binary files a/app/src/main/res/drawable-ldpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_eth.webp b/app/src/main/res/drawable-ldpi/ic_eth.webp new file mode 100644 index 0000000000..dc633be363 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-ldpi/ic_qcx.png b/app/src/main/res/drawable-ldpi/ic_qcx.png deleted file mode 100644 index 68f2ded287..0000000000 Binary files a/app/src/main/res/drawable-ldpi/ic_qcx.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_qcx.webp b/app/src/main/res/drawable-ldpi/ic_qcx.webp new file mode 100644 index 0000000000..9cea22a163 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_qcx.webp differ diff --git a/app/src/main/res/drawable-mdpi/ic_eth.png b/app/src/main/res/drawable-mdpi/ic_eth.png deleted file mode 100644 index e01e76371f..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_eth.webp b/app/src/main/res/drawable-mdpi/ic_eth.webp new file mode 100644 index 0000000000..befd41d571 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-mdpi/meet_tangem.png b/app/src/main/res/drawable-mdpi/meet_tangem.png deleted file mode 100644 index ef04e35830..0000000000 Binary files a/app/src/main/res/drawable-mdpi/meet_tangem.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/meet_tangem.webp b/app/src/main/res/drawable-mdpi/meet_tangem.webp new file mode 100644 index 0000000000..7bd5ec80c5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/meet_tangem.webp differ diff --git a/app/src/main/res/drawable-mdpi/revolutionary_wallet.png b/app/src/main/res/drawable-mdpi/revolutionary_wallet.png deleted file mode 100644 index 4dba3bffa4..0000000000 Binary files a/app/src/main/res/drawable-mdpi/revolutionary_wallet.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/revolutionary_wallet.webp b/app/src/main/res/drawable-mdpi/revolutionary_wallet.webp new file mode 100644 index 0000000000..dfc7b106a9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/revolutionary_wallet.webp differ diff --git a/app/src/main/res/drawable-mdpi/wallet_for_everyone.png b/app/src/main/res/drawable-mdpi/wallet_for_everyone.png deleted file mode 100644 index 9ec8c92210..0000000000 Binary files a/app/src/main/res/drawable-mdpi/wallet_for_everyone.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/wallet_for_everyone.webp b/app/src/main/res/drawable-mdpi/wallet_for_everyone.webp new file mode 100644 index 0000000000..bf0ea6cf64 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/wallet_for_everyone.webp differ diff --git a/app/src/main/res/drawable-xhdpi/ic_eth.png b/app/src/main/res/drawable-xhdpi/ic_eth.png deleted file mode 100644 index 5e5807bf8b..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_eth.webp b/app/src/main/res/drawable-xhdpi/ic_eth.webp new file mode 100644 index 0000000000..ba04cee99d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-xhdpi/meet_tangem.png b/app/src/main/res/drawable-xhdpi/meet_tangem.png deleted file mode 100644 index 958f83e0a6..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/meet_tangem.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/meet_tangem.webp b/app/src/main/res/drawable-xhdpi/meet_tangem.webp new file mode 100644 index 0000000000..9bc7cba662 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/meet_tangem.webp differ diff --git a/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png deleted file mode 100644 index e72d2ff281..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/revolutionary_wallet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/revolutionary_wallet.webp b/app/src/main/res/drawable-xhdpi/revolutionary_wallet.webp new file mode 100644 index 0000000000..ee0435caf1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/revolutionary_wallet.webp differ diff --git a/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png deleted file mode 100644 index 773a3bb463..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/wallet_for_everyone.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/wallet_for_everyone.webp b/app/src/main/res/drawable-xhdpi/wallet_for_everyone.webp new file mode 100644 index 0000000000..2eec0c7074 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/wallet_for_everyone.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_eth.png b/app/src/main/res/drawable-xxhdpi/ic_eth.png deleted file mode 100644 index 1e7a0e7952..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_eth.webp b/app/src/main/res/drawable-xxhdpi/ic_eth.webp new file mode 100644 index 0000000000..440f1fea5c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/meet_tangem.png b/app/src/main/res/drawable-xxhdpi/meet_tangem.png deleted file mode 100644 index 4d5144346d..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/meet_tangem.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/meet_tangem.webp b/app/src/main/res/drawable-xxhdpi/meet_tangem.webp new file mode 100644 index 0000000000..941a671afb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/meet_tangem.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png deleted file mode 100644 index 83a8bc7963..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.webp b/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.webp new file mode 100644 index 0000000000..360f004149 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/revolutionary_wallet.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png deleted file mode 100644 index 18b099256b..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.webp b/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.webp new file mode 100644 index 0000000000..408f94af76 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/wallet_for_everyone.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_eth.png b/app/src/main/res/drawable-xxxhdpi/ic_eth.png deleted file mode 100644 index 74ead90795..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_eth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_eth.webp b/app/src/main/res/drawable-xxxhdpi/ic_eth.webp new file mode 100644 index 0000000000..8a3417ce7b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_eth.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/meet_tangem.png b/app/src/main/res/drawable-xxxhdpi/meet_tangem.png deleted file mode 100644 index a84bb7b804..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/meet_tangem.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/meet_tangem.webp b/app/src/main/res/drawable-xxxhdpi/meet_tangem.webp new file mode 100644 index 0000000000..8ca89394bc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/meet_tangem.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png b/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png deleted file mode 100644 index bc0d94be7f..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.webp b/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.webp new file mode 100644 index 0000000000..9568833439 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/revolutionary_wallet.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png b/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png deleted file mode 100644 index 1d99d407ab..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.webp b/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.webp new file mode 100644 index 0000000000..5dc7baec82 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/wallet_for_everyone.webp differ diff --git a/app/src/main/res/drawable/currency0.png b/app/src/main/res/drawable/currency0.png deleted file mode 100644 index 0d293ec8bb..0000000000 Binary files a/app/src/main/res/drawable/currency0.png and /dev/null differ diff --git a/app/src/main/res/drawable/currency0.webp b/app/src/main/res/drawable/currency0.webp new file mode 100644 index 0000000000..a55afc36fe Binary files /dev/null and b/app/src/main/res/drawable/currency0.webp differ diff --git a/app/src/main/res/drawable/currency1.png b/app/src/main/res/drawable/currency1.png deleted file mode 100644 index 37144efffe..0000000000 Binary files a/app/src/main/res/drawable/currency1.png and /dev/null differ diff --git a/app/src/main/res/drawable/currency1.webp b/app/src/main/res/drawable/currency1.webp new file mode 100644 index 0000000000..b24e1009ff Binary files /dev/null and b/app/src/main/res/drawable/currency1.webp differ diff --git a/app/src/main/res/drawable/currency2.png b/app/src/main/res/drawable/currency2.png deleted file mode 100644 index bd75790221..0000000000 Binary files a/app/src/main/res/drawable/currency2.png and /dev/null differ diff --git a/app/src/main/res/drawable/currency2.webp b/app/src/main/res/drawable/currency2.webp new file mode 100644 index 0000000000..86dfb0f2d4 Binary files /dev/null and b/app/src/main/res/drawable/currency2.webp differ diff --git a/app/src/main/res/drawable/currency3.png b/app/src/main/res/drawable/currency3.png deleted file mode 100644 index c517578807..0000000000 Binary files a/app/src/main/res/drawable/currency3.png and /dev/null differ diff --git a/app/src/main/res/drawable/currency3.webp b/app/src/main/res/drawable/currency3.webp new file mode 100644 index 0000000000..a6a5da8499 Binary files /dev/null and b/app/src/main/res/drawable/currency3.webp differ diff --git a/app/src/main/res/drawable/currency4.png b/app/src/main/res/drawable/currency4.png deleted file mode 100644 index 9a39d5651d..0000000000 Binary files a/app/src/main/res/drawable/currency4.png and /dev/null differ diff --git a/app/src/main/res/drawable/currency4.webp b/app/src/main/res/drawable/currency4.webp new file mode 100644 index 0000000000..517c55c0f7 Binary files /dev/null and b/app/src/main/res/drawable/currency4.webp differ diff --git a/app/src/main/res/drawable/dapps0.png b/app/src/main/res/drawable/dapps0.png deleted file mode 100644 index 6152b1215b..0000000000 Binary files a/app/src/main/res/drawable/dapps0.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps0.webp b/app/src/main/res/drawable/dapps0.webp new file mode 100644 index 0000000000..65809f5fe4 Binary files /dev/null and b/app/src/main/res/drawable/dapps0.webp differ diff --git a/app/src/main/res/drawable/dapps1.png b/app/src/main/res/drawable/dapps1.png deleted file mode 100644 index 2d4effaf8b..0000000000 Binary files a/app/src/main/res/drawable/dapps1.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps1.webp b/app/src/main/res/drawable/dapps1.webp new file mode 100644 index 0000000000..833c952010 Binary files /dev/null and b/app/src/main/res/drawable/dapps1.webp differ diff --git a/app/src/main/res/drawable/dapps2.png b/app/src/main/res/drawable/dapps2.png deleted file mode 100644 index f7643c4ec7..0000000000 Binary files a/app/src/main/res/drawable/dapps2.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps2.webp b/app/src/main/res/drawable/dapps2.webp new file mode 100644 index 0000000000..a13cb38ba8 Binary files /dev/null and b/app/src/main/res/drawable/dapps2.webp differ diff --git a/app/src/main/res/drawable/dapps3.png b/app/src/main/res/drawable/dapps3.png deleted file mode 100644 index 9e0110179b..0000000000 Binary files a/app/src/main/res/drawable/dapps3.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps3.webp b/app/src/main/res/drawable/dapps3.webp new file mode 100644 index 0000000000..99f594fd3d Binary files /dev/null and b/app/src/main/res/drawable/dapps3.webp differ diff --git a/app/src/main/res/drawable/dapps4.png b/app/src/main/res/drawable/dapps4.png deleted file mode 100644 index 4eae3c0985..0000000000 Binary files a/app/src/main/res/drawable/dapps4.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps4.webp b/app/src/main/res/drawable/dapps4.webp new file mode 100644 index 0000000000..15704d6a6e Binary files /dev/null and b/app/src/main/res/drawable/dapps4.webp differ diff --git a/app/src/main/res/drawable/dapps5.png b/app/src/main/res/drawable/dapps5.png deleted file mode 100644 index 0c581624c7..0000000000 Binary files a/app/src/main/res/drawable/dapps5.png and /dev/null differ diff --git a/app/src/main/res/drawable/dapps5.webp b/app/src/main/res/drawable/dapps5.webp new file mode 100644 index 0000000000..5d6370f05a Binary files /dev/null and b/app/src/main/res/drawable/dapps5.webp differ diff --git a/app/src/main/res/drawable/ic_voyr.png b/app/src/main/res/drawable/ic_voyr.png deleted file mode 100644 index 545990d041..0000000000 Binary files a/app/src/main/res/drawable/ic_voyr.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_voyr.webp b/app/src/main/res/drawable/ic_voyr.webp new file mode 100644 index 0000000000..2343599d4b Binary files /dev/null and b/app/src/main/res/drawable/ic_voyr.webp differ diff --git a/app/src/main/res/drawable/img_salt_pay_visa.png b/app/src/main/res/drawable/img_salt_pay_visa.png deleted file mode 100644 index 02b199c1e5..0000000000 Binary files a/app/src/main/res/drawable/img_salt_pay_visa.png and /dev/null differ diff --git a/app/src/main/res/drawable/inset_splash.xml b/app/src/main/res/drawable/inset_splash.xml new file mode 100644 index 0000000000..1d66dc1301 --- /dev/null +++ b/app/src/main/res/drawable/inset_splash.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_logo.xml b/app/src/main/res/drawable/splash_logo.xml new file mode 100644 index 0000000000..29cb6365cf --- /dev/null +++ b/app/src/main/res/drawable/splash_logo.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml deleted file mode 100644 index 34b57671aa..0000000000 --- a/app/src/main/res/layout-h680dp/layout_onboarding_saltpay_kyc_in_progress.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 59a23e9ab4..821f0d3ef8 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -221,13 +221,6 @@ - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml index c3dadcdb4b..523b68c535 100644 --- a/app/src/main/res/layout/fragment_shop.xml +++ b/app/src/main/res/layout/fragment_shop.xml @@ -132,7 +132,7 @@ android:paddingStart="3dp" android:paddingTop="3dp" android:paddingEnd="3dp" - app:layout_constraintBottom_toTopOf="@id/v_shipping_discount_background" + app:layout_constraintBottom_toTopOf="@id/ll_total" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:selectionRequired="true" @@ -156,162 +156,125 @@ app:chipMinTouchTargetSize="0dp" /> - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + app:layout_constraintBottom_toTopOf="@id/ll_buttons"> - + - + - + - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml b/app/src/main/res/layout/item_salt_pay_tx_history_date.xml deleted file mode 100644 index a21c2dbd6e..0000000000 --- a/app/src/main/res/layout/item_salt_pay_tx_history_date.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml b/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml deleted file mode 100644 index 72aff47fa0..0000000000 --- a/app/src/main/res/layout/item_salt_pay_tx_history_shimmer.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay.xml b/app/src/main/res/layout/layout_onboarding_saltpay.xml deleted file mode 100644 index fbb3534b9b..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml b/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml deleted file mode 100644 index 5e3ff40fb1..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_connect_card.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml deleted file mode 100644 index 0114973c36..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_in_progress.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml b/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml deleted file mode 100644 index 4a9ef0a9e2..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_kyc_start.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml b/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml deleted file mode 100644 index 96562a0c1f..0000000000 --- a/app/src/main/res/layout/layout_onboarding_saltpay_pin_code.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_balance.xml b/app/src/main/res/layout/layout_salt_pay_balance.xml deleted file mode 100644 index 8ce65d0b6b..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_balance.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_tx_history.xml b/app/src/main/res/layout/layout_salt_pay_tx_history.xml deleted file mode 100644 index 2aef592910..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_tx_history.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_salt_pay_wallet.xml b/app/src/main/res/layout/layout_salt_pay_wallet.xml deleted file mode 100644 index 4712e76937..0000000000 --- a/app/src/main/res/layout/layout_salt_pay_wallet.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address_payid.xml index 3635b30122..1bf69a649f 100644 --- a/app/src/main/res/layout/layout_send_address_payid.xml +++ b/app/src/main/res/layout/layout_send_address_payid.xml @@ -28,7 +28,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - #F2F2F2 #090E13 - #14212121 @color/colorSecondary @@ -74,5 +73,7 @@ #656565 #919191 + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9356f81daf..6c451317c2 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -12,6 +12,12 @@ + @@ -97,19 +103,6 @@ 4sp - -