diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d3445cf24..4f61d629c7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,9 +37,12 @@ dependencies { implementation(project(":features:swap:data")) implementation(project(":features:tester:api")) implementation(project(":features:tester:impl")) + implementation(project(":features:wallet:api")) + implementation(project(":features:wallet:impl")) /** 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) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b9fd68f4ee..682ebe6588 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,7 @@ () override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) systemActions() store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - tangemSdk = TangemSdk.initWithBiometrics(this, TangemSdkManager.config) - tangemSdkManager = TangemSdkManager(tangemSdk, this) + tangemSdk = injectedTangemSdk + tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager appStateHolder.tangemSdk = tangemSdk backupService = BackupService.init(tangemSdk, this) @@ -104,7 +120,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ), ) - store.dispatch(DaggerGraphAction.SetActivityDependencies(testerRouter)) + store.dispatch( + DaggerGraphAction.SetActivityDependencies( + testerRouter = testerRouter, + scanCardUseCase = scanCardUseCase, + walletRouter = walletRouter, + ), + ) } private fun initUserWalletsListManager() { diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b224a7f81a..65d7a0c72c 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -21,6 +21,7 @@ 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 @@ -133,6 +134,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var preferencesDataSource: PreferencesDataSource + @Inject + lateinit var walletFeatureToggles: WalletFeatureToggles + override fun onCreate() { super.onCreate() @@ -146,6 +150,7 @@ class TapApplication : Application(), ImageLoaderFactory { assetReader = assetReader, networkConnectionManager = networkConnectionManager, customTokenFeatureToggles = customTokenFeatureToggles, + walletFeatureToggles = walletFeatureToggles, ), ), ) 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..a66fd6caa0 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.* @@ -118,9 +117,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/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/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/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/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 806a46dd1d..f45d14c864 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -1,11 +1,7 @@ package com.tangem.tap.common.extensions import android.os.Bundle -import androidx.fragment.app.DialogFragment -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.* import com.tangem.feature.referral.ReferralFragment import com.tangem.feature.swap.presentation.SwapFragment import com.tangem.tap.common.redux.navigation.AppScreen @@ -138,7 +134,18 @@ private fun fragmentFactory(screen: AppScreen): Fragment { AppScreen.OnboardingWallet -> OnboardingWalletFragment() AppScreen.OnboardingTwins -> TwinsCardsFragment() AppScreen.OnboardingOther -> OnboardingOtherCardsFragment() - AppScreen.Wallet -> WalletFragment() + AppScreen.Wallet -> { + val featureToggles = store.state.daggerGraphState.get( + getDependency = DaggerGraphState::walletFeatureToggles, + ) + if (featureToggles.isRedesignedScreenEnabled) { + store.state.daggerGraphState + .get(getDependency = DaggerGraphState::walletRouter) + .getEntryFragment() + } else { + WalletFragment() + } + } AppScreen.Send -> SendFragment() AppScreen.Details -> DetailsFragment() AppScreen.DetailsSecurity -> SecurityModeFragment() 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/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/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt new file mode 100644 index 0000000000..96639f64e4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.di + +import android.content.Context +import androidx.fragment.app.FragmentActivity +import com.tangem.TangemSdk +import com.tangem.domain.card.ScanCardUseCase +import com.tangem.sdk.extensions.initWithBiometrics +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository +import com.tangem.tap.userTokensRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped + +@Module +@InstallIn(ActivityComponent::class) +internal object ActivityModule { + + @Provides + @ActivityScoped + fun provideTangemSdk(@ActivityContext context: Context): TangemSdk { + return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config) + } + + @Provides + @ActivityScoped + fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager { + return TangemSdkManager(tangemSdk, context) + } + + @Provides + @ActivityScoped + fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase { + return ScanCardUseCase( + tangemSdk = tangemSdk, + scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager), + ) + } +} \ 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/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 new file mode 100644 index 0000000000..f885b4fa13 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -0,0 +1,177 @@ +package com.tangem.tap.domain.scanCard + +import com.tangem.common.CompletionResult +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.core.analytics.Analytics +import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.domain.common.extensions.withMainContext +import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* +import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor +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 +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.products.twins.redux.TwinCardsAction +import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +internal object LegacyScanProcessor { + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return tangemSdkManager.scanProduct( + userTokensRepository, + cardId, + allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = withMainContext { + onProgressStateChange(true) + onScanStateChange(true) + + tangemSdkManager.changeDisplayedCardIdNumbersCount(null) + + val result = tangemSdkManager.scanProduct( + userTokensRepository = userTokensRepository, + cardId = cardId, + ) + + store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + + result + .doOnFailure { error -> + onScanStateChange(false) + onFailure(error) + } + .doOnSuccess { scanResponse -> + tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) + + onScanStateChange(false) + sendAnalytics(analyticsEvent, scanResponse) + + showDisclaimerIfNeed( + scanResponse = scanResponse, + disclaimerWillShow = disclaimerWillShow, + onFailure = onFailure, + nextHandler = { scanResponse2 -> + onScanSuccess( + scanResponse = scanResponse2, + onProgressStateChange = onProgressStateChange, + onSuccess = onSuccess, + onWalletNotCreated = onWalletNotCreated, + ) + }, + ) + } + } + + private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { + analyticsEvent?.let { + // this workaround needed to send CardWasScannedEvent without adding a context + val interceptor = CardContextInterceptor(scanResponse) + val params = it.params.toMutableMap() + interceptor.intercept(params) + it.params = params.toMap() + + Analytics.send(it) + } + } + + private suspend inline fun showDisclaimerIfNeed( + scanResponse: ScanResponse, + crossinline disclaimerWillShow: () -> Unit = {}, + crossinline nextHandler: suspend (ScanResponse) -> Unit, + crossinline onFailure: suspend (error: TangemError) -> Unit, + ) { + val disclaimer = scanResponse.card.createDisclaimer() + store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) + + if (disclaimer.isAccepted()) { + nextHandler(scanResponse) + } else { + scope.launch { + delay(DELAY_SDK_DIALOG_CLOSE) + disclaimerWillShow() + store.dispatchWithMain( + DisclaimerAction.Show( + fromScreen = AppScreen.Home, + callback = DisclaimerCallback( + onAccept = { + scope.launch(Dispatchers.Main) { + nextHandler(scanResponse) + } + }, + onDismiss = { + scope.launch(Dispatchers.Main) { + onFailure(TangemSdkError.UserCancelled()) + } + }, + ), + ), + ) + } + } + } + + @Suppress("LongMethod", "MagicNumber") + private suspend inline fun onScanSuccess( + scanResponse: ScanResponse, + crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + crossinline onWalletNotCreated: suspend () -> Unit, + crossinline onSuccess: suspend (ScanResponse) -> Unit, + ) { + val globalState = store.state.globalState + val tapWalletManager = globalState.tapWalletManager + tapWalletManager.updateConfigManager(scanResponse) + + store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) + + 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) + } + } + } + + private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + onProgressStateChange(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt index 86f790ee67..0546a79db5 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt @@ -1,49 +1,31 @@ package com.tangem.tap.domain.scanCard +import com.tangem.common.CompletionResult 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.util.cardTypesResolver -import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse -import com.tangem.operations.backup.BackupService -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.backupService -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.redux.global.GlobalAction -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -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 com.tangem.tap.preferencesStorage -import com.tangem.tap.scope +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -// TODO: Create repository for that -object ScanCardProcessor { +// TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead +internal object ScanCardProcessor { + private val isNewCardScanningEnabled: Boolean + get() = store.state.daggerGraphState + .get(DaggerGraphState::customTokenFeatureToggles) + .isNewCardScanningEnabled + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + return if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } else { + LegacyScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) + } + } + + @Suppress("LongParameterList") suspend fun scan( analyticsEvent: AnalyticsEvent? = null, cardId: String? = null, @@ -53,212 +35,29 @@ object ScanCardProcessor { disclaimerWillShow: () -> Unit = {}, onFailure: suspend (error: TangemError) -> Unit = {}, onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, - ) = withMainContext { - onProgressStateChange(true) - onScanStateChange(true) - - tangemSdkManager.changeDisplayedCardIdNumbersCount(null) - - val result = tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - cardId = cardId, - ) - - store.dispatchOnMain(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - - result - .doOnFailure { error -> - onScanStateChange(false) - onFailure(error) - } - .doOnSuccess { scanResponse -> - tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse) - - onScanStateChange(false) - sendAnalytics(analyticsEvent, scanResponse) - - checkForUnfinishedBackupForSaltPay( - backupService = backupService, - scanResponse = scanResponse, - onFailure = onFailure, - nextHandler = { scanResponse1 -> - showDisclaimerIfNeed( - scanResponse = scanResponse1, - disclaimerWillShow = disclaimerWillShow, - onFailure = onFailure, - nextHandler = { scanResponse2 -> - onScanSuccess( - scanResponse = scanResponse2, - onProgressStateChange = onProgressStateChange, - onSuccess = onSuccess, - onWalletNotCreated = onWalletNotCreated, - onFailure = onFailure, - ) - }, - ) - }, - ) - } - } - - private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) { - analyticsEvent?.let { - // this workaround needed to send CardWasScannedEvent without adding a context - val interceptor = CardContextInterceptor(scanResponse) - val params = it.params.toMutableMap() - interceptor.intercept(params) - it.params = params.toMap() - - Analytics.send(it) - } - } - - /** - * 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)) + if (isNewCardScanningEnabled) { + UseCaseScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } else { - nextHandler(scanResponse) + LegacyScanProcessor.scan( + analyticsEvent, + cardId, + onProgressStateChange, + onScanStateChange, + onWalletNotCreated, + disclaimerWillShow, + onFailure, + onSuccess, + ) } } - - private suspend inline fun showDisclaimerIfNeed( - scanResponse: ScanResponse, - crossinline disclaimerWillShow: () -> Unit = {}, - crossinline nextHandler: suspend (ScanResponse) -> Unit, - crossinline onFailure: suspend (error: TangemError) -> Unit, - ) { - val disclaimer = scanResponse.card.createDisclaimer() - store.dispatchOnMain(DisclaimerAction.SetDisclaimer(disclaimer)) - - if (disclaimer.isAccepted()) { - nextHandler(scanResponse) - } else { - scope.launch { - delay(DELAY_SDK_DIALOG_CLOSE) - disclaimerWillShow() - dispatchOnMain( - DisclaimerAction.Show( - fromScreen = AppScreen.Home, - callback = DisclaimerCallback( - onAccept = { - scope.launch(Dispatchers.Main) { - nextHandler(scanResponse) - } - }, - onDismiss = { - scope.launch(Dispatchers.Main) { - onFailure(TangemSdkError.UserCancelled()) - } - }, - ), - ), - ) - } - } - } - - @Suppress("LongMethod", "MagicNumber") - private suspend inline fun onScanSuccess( - scanResponse: ScanResponse, - 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 - tapWalletManager.updateConfigManager(scanResponse) - - 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)) - } - } - } - } 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) - } - } - } - } - - private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) { - delay(DELAY_SDK_DIALOG_CLOSE) - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - onProgressStateChange(false) - } } \ No newline at end of file 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 new file mode 100644 index 0000000000..45a463343a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -0,0 +1,113 @@ +package com.tangem.tap.domain.scanCard + +import arrow.fx.coroutines.resourceScope +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +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.common.extensions.dispatchOnMain +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.domain.scanCard.chains.* +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.preferencesStorage +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.store +import kotlinx.coroutines.delay + +internal object UseCaseScanProcessor { + private val scanCardExceptionConverter = ScanCardExceptionConverter() + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult { + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + return scanCardUseCase(cardId, allowsRequestAccessCodeFromRepository) + .fold( + ifLeft = { CompletionResult.Failure(scanCardExceptionConverter.convertBack(it)) }, + ifRight = { CompletionResult.Success(it) }, + ) + } + + @Suppress("LongParameterList") + suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, + ) = progressScope(onProgressStateChange) { + onScanStateChange(true) + + val scanCardUseCase = store.state.daggerGraphState.get(DaggerGraphState::scanCardUseCase) + val chains = buildList { + add(ScanningFinishedChain { onScanStateChange(false) }) + if (analyticsEvent != null) { + add(AnalyticsChain(analyticsEvent)) + } + add(DisclaimerChain(store, disclaimerWillShow)) + add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage)) + } + + scanCardUseCase(cardId, afterScanChains = chains) + .map { onSuccess(it) } + .mapLeft { proceedWithException(it, onWalletNotCreated, onFailure) } + } + + private suspend fun proceedWithException( + exception: ScanCardException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanCardException.ChainException -> proceedWithScanChainException( + exception, + onWalletNotCreated, + onFailure, + ) + is ScanCardException.UnknownException, + is ScanCardException.UserCancelled, + is ScanCardException.WrongAccessCode, + is ScanCardException.WrongCardId, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun proceedWithScanChainException( + exception: ScanCardException.ChainException, + onWalletNotCreated: suspend () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + ) { + when (exception) { + is ScanChainException.OnboardingNeeded -> { + navigateTo(exception.onboardingRoute) + onWalletNotCreated() + } + is ScanChainException.DisclaimerWasCanceled, + -> onFailure(scanCardExceptionConverter.convertBack(exception)) + } + } + + private suspend fun progressScope( + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + action: suspend () -> Unit, + ) = resourceScope { + install( + acquire = { onProgressStateChange(true) }, + release = { _, _ -> onProgressStateChange(false) }, + ) + + action() + } + + private suspend inline fun navigateTo(screen: AppScreen) { + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) + } +} \ No newline at end of file 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/repository/DefaultScanCardRepository.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt new file mode 100644 index 0000000000..2de676ba6c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/repository/DefaultScanCardRepository.kt @@ -0,0 +1,38 @@ +package com.tangem.tap.domain.scanCard.repository + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.common.CompletionResult +import com.tangem.domain.card.ScanCardException +import com.tangem.domain.card.repository.ScanCardRepository +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.domain.TangemSdkManager +import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter +import com.tangem.tap.domain.tokens.UserTokensRepository + +internal class DefaultScanCardRepository( + // FIXME: The repository should not depend on another repository. + // But now we need to provide loadBlockchainsToDerive() to ScanProductTask and it's hard to move this method from + // UserTokensRepository. + private val userTokensRepository: UserTokensRepository, + private val tangemSdkManager: TangemSdkManager, +) : ScanCardRepository { + + private val exceptionConverter = ScanCardExceptionConverter() + + override suspend fun scanCard( + cardId: String?, + allowRequestAccessCodeFromStorage: Boolean, + ): Either = either { + when ( + val result = tangemSdkManager.scanProduct( + cardId = cardId, + userTokensRepository = userTokensRepository, + allowsRequestAccessCodeFromRepository = allowRequestAccessCodeFromStorage, + ) + ) { + is CompletionResult.Success -> result.data + is CompletionResult.Failure -> raise(exceptionConverter.convert(result.error)) + } + } +} \ 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 new file mode 100644 index 0000000000..53062e6918 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/utils/ScanCardExceptionConverter.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.domain.scanCard.utils + +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.utils.converter.TwoWayConverter + +internal class ScanCardExceptionConverter : TwoWayConverter { + override fun convert(value: TangemError): ScanCardException = when (value) { + is TangemSdkError -> convertTangemSdkError(value) + else -> ScanCardException.UnknownException(value) + } + + override fun convertBack(value: ScanCardException): TangemError { + return when (value) { + is ScanCardException.UnknownException -> TangemSdkError.ExceptionError(value) + is ScanCardException.UserCancelled -> TangemSdkError.UserCancelled() + is ScanCardException.WrongAccessCode -> TangemSdkError.WrongAccessCode() + is ScanCardException.WrongCardId -> TangemSdkError.WrongCardNumber(value.cardId) + is ScanCardException.ChainException -> concertScanChainException(value) + } + } + + private fun convertTangemSdkError(value: TangemSdkError): ScanCardException = when (value) { + is TangemSdkError.UserCancelled -> ScanCardException.UserCancelled + is TangemSdkError.WrongAccessCode, + is TangemSdkError.WrongPasscode, + -> ScanCardException.WrongAccessCode + is TangemSdkError.WrongCardNumber -> ScanCardException.WrongCardId(value.cardId) + else -> ScanCardException.UnknownException(value) + } + + private fun concertScanChainException(value: ScanCardException.ChainException): TangemError { + return when (val e = value as? ScanChainException) { + is ScanChainException.DisclaimerWasCanceled -> TangemSdkError.UserCancelled() + is ScanChainException.OnboardingNeeded, + null, + -> TangemSdkError.ExceptionError(e?.cause) + } + } +} \ No newline at end of file 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 255d8611c5..bb1c27c80e 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 @@ -47,6 +47,7 @@ class UserTokensRepository( storageService.saveUserTokens(userWalletId = userWalletId, tokens = userTokens) } + // FIXME: Move to data layer suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { val userWalletId = getUserWalletId(card) ?: return@withContext emptyList() val blockchainNetworks = loadTokensOffline(userWalletId = userWalletId).toBlockchainNetworks() @@ -62,6 +63,7 @@ class UserTokensRepository( return storageService.getUserTokens(userWalletId = userWalletId) ?: emptyList() } + // FIXME: Move to user wallet config private fun loadDemoCurrencies(): List { return DemoHelper.config.demoBlockchains .map { blockchain -> 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/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt index 6de26f4bd2..b64f9efd14 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/api/featuretoggles/CustomTokenFeatureToggles.kt @@ -9,4 +9,6 @@ interface CustomTokenFeatureToggles { /** Availability of redesigned screen (internal feature) */ val isRedesignedScreenEnabled: Boolean + + val isNewCardScanningEnabled: Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt index fbdc41b2c0..a19dde5403 100644 --- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/featuretoggles/DefaultCustomTokenFeatureToggles.kt @@ -16,4 +16,7 @@ internal class DefaultCustomTokenFeatureToggles( override val isRedesignedScreenEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED") + + override val isNewCardScanningEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "NEW_CARD_SCANNING_ENABLED") } \ 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/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index b10ad9de2a..acc0130ea9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -24,6 +24,7 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction 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.UserWalletsListManager import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation @@ -32,6 +33,13 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.foregroundActivityObserver +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager +import com.tangem.tap.walletStoresManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -72,10 +80,7 @@ class DetailsMiddleware { is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanProduct( - userTokensRepository = userTokensRepository, - allowsRequestAccessCodeFromRepository = true, - ) + ScanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) .doOnSuccess { scanResponse -> // if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) 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/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/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/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index f534813ecb..9d7d775e1d 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -3,9 +3,11 @@ package com.tangem.tap.features.home.redux import com.tangem.common.doOnFailure import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess +import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.AnalyticsEvent import com.tangem.domain.models.scan.ScanResponse +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -25,11 +27,6 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction -import com.tangem.tap.preferencesStorage -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -80,6 +77,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) + ScanCardProcessor.scan( analyticsEvent = analyticsEvent, onProgressStateChange = { showProgress -> @@ -93,6 +91,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { store.dispatch(HomeAction.ScanInProgress(scanInProgress)) }, onFailure = { + Timber.e(it, "Unable to scan card") changeButtonState(ButtonState.ENABLED) }, onSuccess = { scanResponse -> @@ -101,26 +100,23 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { ) } -fun proceedWithScanResponse(scanResponse: ScanResponse) { - scope.launch { - val userWallet = UserWalletBuilder(scanResponse).build() - if (userWallet == null) { - Timber.e("User wallet not created") - return@launch - } - - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Unable to save user wallet") - } - .doOnSuccess { - scope.launch { store.onUserWalletSelected(userWallet = userWallet) } - } - .doOnResult { - store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) - navigateTo(AppScreen.Wallet) - } +private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build().guard { + Timber.e("User wallet not created") + return@launch } + + userWalletsListManager.save(userWallet) + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } + .doOnSuccess { + scope.launch { store.onUserWalletSelected(userWallet = userWallet) } + } + .doOnResult { + store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Card)) + navigateTo(AppScreen.Wallet) + } } private suspend fun navigateTo(appScreen: AppScreen) { 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 b5dfe53817..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/saltPay/GnosisRegistrator.kt +++ /dev/null @@ -1,230 +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.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 = 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, - fee = 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 -> { - if (this.data.size != 3) { - Result.Failure(BlockchainSdkError.FailedToLoadFee) - } else { - Result.Success(this.data[1]) - } - } - 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 719cd930c3..5dee274734 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 @@ -26,9 +26,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 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/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/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index d7c7bd6d85..607d8c63c4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -15,13 +15,12 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.UserWallet +import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog import com.tangem.tap.scope import com.tangem.tap.store -import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager import kotlinx.coroutines.Dispatchers @@ -96,33 +95,27 @@ class MultiWalletMiddleware { return } store.state.globalState.topUpController?.scanToGetDerivations() - scanAndUpdateCard(selectedUserWallet, walletState) + scanAndUpdateCard(selectedUserWallet) } else -> {} } } - private fun scanAndUpdateCard(selectedUserWallet: UserWallet, state: WalletState?) = - scope.launch(Dispatchers.Default) { - tangemSdkManager.scanProduct( - cardId = selectedUserWallet.cardId, - userTokensRepository = userTokensRepository, - additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, - allowsRequestAccessCodeFromRepository = true, - ) - .flatMap { scanResponse -> - userWalletsListManager.update( - userWalletId = selectedUserWallet.walletId, - update = { userWallet -> - userWallet.copy( - scanResponse = scanResponse, - ) - }, - ) - } - .doOnSuccess { updatedUserWallet -> - store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) - store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) - } - } + private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) { + ScanCardProcessor.scan(selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true) + .flatMap { scanResponse -> + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + } + .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) + } + } } \ 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/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 59336d8989..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)) }, ) } @@ -217,7 +208,7 @@ internal class WalletSelectorMiddleware { private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) - return tangemSdkManager.scanProduct(userTokensRepository) + return ScanCardProcessor.scan() .map { scanResponse -> val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() if (scannedUserWalletId == userWallet.walletId) { @@ -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 36f85b0311..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 @@ -1,8 +1,11 @@ package com.tangem.tap.features.welcome.redux import android.content.Intent -import com.tangem.common.* import com.tangem.common.core.TangemSdkError +import com.tangem.common.doOnFailure +import com.tangem.common.doOnResult +import com.tangem.common.doOnSuccess +import com.tangem.common.flatMap import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -16,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 @@ -60,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)) } @@ -130,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/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index 2952ed0257..99734202b0 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 @@ -1,9 +1,15 @@ 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 org.rekotlin.Action sealed interface DaggerGraphAction : Action { - data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction + data class SetActivityDependencies( + val testerRouter: TesterRouter, + val scanCardUseCase: ScanCardUseCase, + val walletRouter: WalletRouter, + ) : 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 43cb94af5c..a2c7994ce1 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 @@ -14,6 +14,8 @@ object DaggerGraphReducer { return when (action) { is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy( testerRouter = action.testerRouter, + scanCardUseCase = action.scanCardUseCase, + walletRouter = action.walletRouter, ) } } 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 73fc659cf8..b59edc1928 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 @@ -2,7 +2,10 @@ package com.tangem.tap.proxy.redux import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager +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.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles import org.rekotlin.StateType @@ -11,6 +14,9 @@ data class DaggerGraphState( val testerRouter: TesterRouter? = null, val networkConnectionManager: NetworkConnectionManager? = null, val customTokenFeatureToggles: CustomTokenFeatureToggles? = null, + val scanCardUseCase: ScanCardUseCase? = null, + val walletFeatureToggles: WalletFeatureToggles? = null, + val walletRouter: WalletRouter? = 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/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_wallet.xml b/app/src/main/res/layout/fragment_wallet.xml index 6f1a62cabf..b4287625d4 100644 --- a/app/src/main/res/layout/fragment_wallet.xml +++ b/app/src/main/res/layout/fragment_wallet.xml @@ -177,15 +177,6 @@ android:visibility="gone" app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" /> - - - - - - - - - - - - - - - - - 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/values/colors.xml b/app/src/main/res/values/colors.xml index ce094f06b8..3509ec1ee5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -33,7 +33,6 @@ #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 - -