Updated on 2026-08-14
This commit is contained in:
parent
8fd894f5d5
commit
45013d6188
105 changed files with 123 additions and 5898 deletions
|
|
@ -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<GlobalState> {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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<CardTypesResolver, AnalyticsParam.C
|
|||
val type = when {
|
||||
value.isTangemNote() -> 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
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<Wallet> = 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<TransactionData> = wallet.getBlockchainTxHistory()
|
||||
|
||||
fun WalletManager.getTokenTxHistory(token: Token): List<TransactionData> = wallet.getTokenTxHistory(token)
|
||||
|
||||
fun <T> 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<TransactionData> {
|
||||
return historyTransactions.filter {
|
||||
it.contractAddress.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun Wallet.getTokenTxHistory(token: Token): List<TransactionData> {
|
||||
return historyTransactions.filter {
|
||||
it.contractAddress == token.contractAddress
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<TransactionData>?,
|
||||
) {
|
||||
|
||||
data class WalletAddresses(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ class UserWalletIdBuilder private constructor(
|
|||
ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes()
|
||||
ProductType.Note,
|
||||
ProductType.Wallet,
|
||||
ProductType.SaltPay,
|
||||
ProductType.Start2Coin,
|
||||
-> null
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<SaltPayModuleMessage, ConvertedMessage?> {
|
||||
|
||||
override fun convert(message: SaltPayModuleMessage): ConvertedMessage? {
|
||||
return when (message) {
|
||||
is SaltPayModuleError -> SaltPayErrorConverter(context).convert(message)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SaltPayErrorConverter(
|
||||
private val context: Context,
|
||||
) : ModuleMessageConverter<SaltPayModuleError, ConvertedMessage?> {
|
||||
|
||||
override fun convert(message: SaltPayModuleError): ConvertedMessage? = when (message) {
|
||||
is SaltPayActivationError -> SaltPayActivationErrorConverter(context).convert(message)
|
||||
else -> 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<SaltPayActivationError, ConvertedDialogMessage?> {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -5,14 +5,11 @@ import com.tangem.common.core.TangemError
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.AnalyticsEvent
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -23,13 +20,8 @@ import com.tangem.tap.features.disclaimer.createDisclaimer
|
|||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayActivationManagerFactory
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -81,24 +73,16 @@ internal object LegacyScanProcessor {
|
|||
onScanStateChange(false)
|
||||
sendAnalytics(analyticsEvent, scanResponse)
|
||||
|
||||
checkForUnfinishedBackupForSaltPay(
|
||||
backupService = backupService,
|
||||
showDisclaimerIfNeed(
|
||||
scanResponse = scanResponse,
|
||||
disclaimerWillShow = disclaimerWillShow,
|
||||
onFailure = onFailure,
|
||||
nextHandler = { scanResponse1 ->
|
||||
showDisclaimerIfNeed(
|
||||
scanResponse = scanResponse1,
|
||||
disclaimerWillShow = disclaimerWillShow,
|
||||
onFailure = onFailure,
|
||||
nextHandler = { scanResponse2 ->
|
||||
onScanSuccess(
|
||||
scanResponse = scanResponse2,
|
||||
onProgressStateChange = onProgressStateChange,
|
||||
onSuccess = onSuccess,
|
||||
onWalletNotCreated = onWalletNotCreated,
|
||||
onFailure = onFailure,
|
||||
)
|
||||
},
|
||||
nextHandler = { scanResponse2 ->
|
||||
onScanSuccess(
|
||||
scanResponse = scanResponse2,
|
||||
onProgressStateChange = onProgressStateChange,
|
||||
onSuccess = onSuccess,
|
||||
onWalletNotCreated = onWalletNotCreated,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -117,35 +101,6 @@ internal object LegacyScanProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards
|
||||
* see BackupAction.CheckForUnfinishedBackup
|
||||
* If user touches card other than Visa SaltPay - show dialog and block next processing
|
||||
*/
|
||||
private suspend inline fun checkForUnfinishedBackupForSaltPay(
|
||||
backupService: BackupService,
|
||||
scanResponse: ScanResponse,
|
||||
nextHandler: (ScanResponse) -> Unit,
|
||||
onFailure: suspend (error: TangemError) -> Unit,
|
||||
) {
|
||||
if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
|
||||
nextHandler(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
val isTheSamePrimaryCard = backupService.primaryCardId
|
||||
?.let { it == scanResponse.card.cardId }
|
||||
?: false
|
||||
|
||||
if (scanResponse.cardTypesResolver.isSaltPayWallet() || !isTheSamePrimaryCard) {
|
||||
val error = SaltPayActivationError.PutVisaCard
|
||||
SaltPayExceptionHandler.handle(error)
|
||||
onFailure(TangemSdkError.ExceptionError(error))
|
||||
} else {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun showDisclaimerIfNeed(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline disclaimerWillShow: () -> Unit = {},
|
||||
|
|
@ -188,7 +143,6 @@ internal object LegacyScanProcessor {
|
|||
crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
|
||||
crossinline onWalletNotCreated: suspend () -> Unit,
|
||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
||||
) {
|
||||
val globalState = store.state.globalState
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
|
|
@ -196,66 +150,21 @@ internal object LegacyScanProcessor {
|
|||
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
if (cardTypesResolver.isSaltPay()) {
|
||||
if (cardTypesResolver.isSaltPayVisa()) {
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = scanResponse.cardTypesResolver.getBlockchain(),
|
||||
card = scanResponse.card,
|
||||
).create()
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
|
||||
delay(500)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
Analytics.addContext(scanResponse)
|
||||
val isOnboardingCase = result.data
|
||||
if (isOnboardingCase) {
|
||||
onWalletNotCreated()
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatch(OnboardingSaltPayAction.Update(withAnalytics = false))
|
||||
navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
SaltPayExceptionHandler.handle(result.error)
|
||||
onFailure(TangemSdkError.ExceptionError(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
Analytics.addContext(scanResponse)
|
||||
onWalletNotCreated()
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true))
|
||||
val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
navigateTo(appScreen) { onProgressStateChange(it) }
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
onWalletNotCreated()
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
|
||||
navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
if (scanResponse.card.backupStatus?.isActive == false) {
|
||||
val error = SaltPayActivationError.PutVisaCard
|
||||
SaltPayExceptionHandler.handle(error)
|
||||
onFailure(TangemSdkError.ExceptionError(error))
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
onSuccess(scanResponse)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
Analytics.addContext(scanResponse)
|
||||
onWalletNotCreated()
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true))
|
||||
val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
navigateTo(appScreen) { onProgressStateChange(it) }
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
onWalletNotCreated()
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
|
||||
navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
}
|
||||
onSuccess(scanResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.core.analytics.AnalyticsEvent
|
|||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -52,7 +51,6 @@ internal object UseCaseScanProcessor {
|
|||
if (analyticsEvent != null) {
|
||||
add(AnalyticsChain(analyticsEvent))
|
||||
}
|
||||
add(CheckForUnfinishedSaltPayBackupChain(backupService))
|
||||
add(DisclaimerChain(store, disclaimerWillShow))
|
||||
add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager, preferencesStorage))
|
||||
}
|
||||
|
|
@ -91,9 +89,7 @@ internal object UseCaseScanProcessor {
|
|||
navigateTo(exception.onboardingRoute)
|
||||
onWalletNotCreated()
|
||||
}
|
||||
is ScanChainException.PutSaltPayVisaCard,
|
||||
is ScanChainException.DisclaimerWasCanceled,
|
||||
is ScanChainException.CheckForSaltPayOnboardingCaseException,
|
||||
-> onFailure(scanCardExceptionConverter.convertBack(exception))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ScanCardException.ChainException, ScanResponse> {
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -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<ScanCardException.ChainException, ScanResponse> {
|
||||
|
||||
override suspend fun invoke(
|
||||
previousChainResult: ScanResponse,
|
||||
): Either<ScanCardException.ChainException, ScanResponse> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import com.tangem.common.core.TangemError
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.tap.domain.scanCard.chains.ScanChainException
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class ScanCardExceptionConverter : TwoWayConverter<TangemError, ScanCardException> {
|
||||
|
|
@ -35,10 +34,6 @@ internal class ScanCardExceptionConverter : TwoWayConverter<TangemError, ScanCar
|
|||
private fun concertScanChainException(value: ScanCardException.ChainException): TangemError {
|
||||
return when (val e = value as? ScanChainException) {
|
||||
is ScanChainException.DisclaimerWasCanceled -> TangemSdkError.UserCancelled()
|
||||
is ScanChainException.PutSaltPayVisaCard -> TangemSdkError.ExceptionError(
|
||||
SaltPayActivationError.PutVisaCard,
|
||||
)
|
||||
is ScanChainException.CheckForSaltPayOnboardingCaseException,
|
||||
is ScanChainException.OnboardingNeeded,
|
||||
null,
|
||||
-> TangemSdkError.ExceptionError(e?.cause)
|
||||
|
|
|
|||
|
|
@ -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<ScanResponse>) -> 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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WalletDataModel>.updateWithFiatRates(fiatRates: Map<String, Do
|
|||
}
|
||||
}
|
||||
|
||||
internal fun WalletDataModel.updateWithTxHistory(wallet: Wallet): WalletDataModel {
|
||||
return this.copy(
|
||||
historyTransactions = when (currency) {
|
||||
is Currency.Blockchain -> wallet.getBlockchainTxHistory()
|
||||
is Currency.Token -> wallet.getTokenTxHistory(currency.token)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithTxHistories(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithTxHistory(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<WalletDataModel>.updateWithAmounts(wallet: Wallet): List<WalletDataModel> {
|
||||
return this.map { walletData ->
|
||||
walletData.updateWithAmount(wallet)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,13 +32,8 @@ class DetailsViewModel(private val store: Store<AppState>) {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<Boolean> {
|
||||
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<Boolean> =
|
||||
Result.Success(true)
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
fun testProceedToMainScreen(scanResponse: ScanResponse, manager: SaltPayActivationManager): Result<Boolean> =
|
||||
Result.Success(false)
|
||||
}
|
||||
|
|
@ -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<CreateProductWalletTaskResponse>,
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<String>) {
|
||||
cardIds.forEach { cardId ->
|
||||
|
|
@ -514,31 +481,10 @@ internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<Strin
|
|||
.distinct()
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used for SaltPay cards, because discarding unfinished backup is not possible
|
||||
*/
|
||||
private fun finishCardsActivationForDiscardedUnfinishedBackup(cardId: String) {
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
|
||||
/**
|
||||
* SaltPay state maybe not initialized if backup resumed from ResumeFoundUnfinishedBackup action
|
||||
*/
|
||||
private fun initSaltPayOnBackupFinishedIfNeeded(scanResponse: ScanResponse?, state: OnboardingWalletState) {
|
||||
if (state.backupState.isInterruptedBackup) return
|
||||
if (scanResponse == null) return
|
||||
|
||||
if (state.isSaltPay && state.onboardingSaltPayState == null) {
|
||||
val manager = SaltPayActivationManagerFactory(
|
||||
blockchain = scanResponse.cardTypesResolver.getBlockchain(),
|
||||
card = scanResponse.card,
|
||||
).create()
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.SetDependencies(manager))
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.Update(withAnalytics = false))
|
||||
store.dispatchOnMain(OnboardingSaltPayAction.OnSwitchedToSaltPayProcess)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
BackupStep.InitBackup, BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
|
|
@ -550,12 +496,8 @@ private fun handleOnBackPressed(state: OnboardingWalletState) {
|
|||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
}
|
||||
BackupStep.Finished -> {
|
||||
if (state.isSaltPay) {
|
||||
showInterruptOnboardingDialog()
|
||||
} else {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Boolean> {
|
||||
// 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<Unit> {
|
||||
walletManager.safeUpdate().successOr {
|
||||
return Result.Failure(BlockchainSdkError.WrappedThrowable(it.error))
|
||||
}
|
||||
atomicNonce.set(walletManager.txCount)
|
||||
|
||||
return Result.Success(Unit)
|
||||
}
|
||||
|
||||
suspend fun sendTransactions(transactions: List<SignedEthereumTransaction>): Result<List<String>> {
|
||||
val results = transactions.map {
|
||||
coroutineScope { async { walletManager.sendRaw(it.compiledTransaction, it.signature) } }
|
||||
}.awaitAll()
|
||||
|
||||
val errors = results.filterIsInstance<SimpleResult.Failure>()
|
||||
return if (errors.isEmpty()) {
|
||||
Result.Success(listOf())
|
||||
} else {
|
||||
Result.Failure(errors[0].error)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun makeSetSpendLimitTx(value: BigDecimal): Result<CompiledEthereumTransaction> {
|
||||
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<CompiledEthereumTransaction> {
|
||||
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<CompiledEthereumTransaction> {
|
||||
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<CompiledEthereumTransaction> {
|
||||
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<Amount> {
|
||||
// return Result.Success(Amount(BigDecimal(0.1), walletManager.wallet.blockchain)) // test
|
||||
return walletManager.getAllowance(addressTreasureSafe, token)
|
||||
}
|
||||
|
||||
suspend fun transferFrom(amountToClaim: BigDecimal, signer: TransactionSigner): Result<Unit> {
|
||||
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<Unit> {
|
||||
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<Unit> {
|
||||
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<List<Amount>>.extractFeeAmount(): Result<Amount> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.saltPay
|
||||
|
||||
import com.tangem.common.Filter
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface PinFilter : Filter<String>
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Unit> {
|
||||
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<Unit> {
|
||||
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<RegistrationResponse.Item> {
|
||||
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<RegistrationResponse>()
|
||||
.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<AttestationResponse> {
|
||||
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<SignedEthereumTransaction>): BlockchainResult<List<String>> {
|
||||
return gnosisRegistrator.sendTransactions(signedTransactions)
|
||||
}
|
||||
|
||||
suspend fun registerWallet(
|
||||
attestResponse: AttestWalletKeyResponse,
|
||||
pinCode: String,
|
||||
): Result<RegisterWalletResponse> {
|
||||
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<Amount> {
|
||||
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<Unit> {
|
||||
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<BigDecimal> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SignedEthereumTransaction>,
|
||||
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<RegistrationTaskResponse> {
|
||||
|
||||
private var generateOTPResponse: GenerateOTPResponse? = null
|
||||
private var attestWalletResponse: AttestWalletKeyResponse? = null
|
||||
private var signedTransactions: List<SignedEthereumTransaction> = listOf()
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<RegistrationTaskResponse>) {
|
||||
generateOTP(session, callback)
|
||||
}
|
||||
|
||||
private fun generateOTP(session: CardSession, callback: CompletionCallback<RegistrationTaskResponse>) {
|
||||
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<RegistrationTaskResponse>) {
|
||||
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<RegistrationTaskResponse>,
|
||||
) {
|
||||
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<CompiledEthereumTransaction>,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<RegistrationTaskResponse>,
|
||||
) {
|
||||
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<RegistrationTaskResponse>) {
|
||||
if (signedTransactions.isEmpty() || attestWalletResponse == null) {
|
||||
callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
|
||||
} else {
|
||||
val result = RegistrationTaskResponse(signedTransactions, attestWalletResponse!!)
|
||||
callback(CompletionResult.Success(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Array<Uri>>? = null
|
||||
private var mediaUri: Uri? = null
|
||||
|
||||
override fun onShowFileChooser(
|
||||
webView: WebView?,
|
||||
filePathCallback: ValueCallback<Array<Uri>>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = ""
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<AppState> = { 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<SaltPayActivationStep> {
|
||||
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<Unit> {
|
||||
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<Filter<String>> = listOf(
|
||||
AllSymbolsTheSameFilter(),
|
||||
)
|
||||
|
||||
if (filters.any { it.filter(pin) }) {
|
||||
throw SaltPayActivationError.WeakPin
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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<MaterialButton>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SeedPhraseViewModel>()
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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<W
|
|||
override fun newStateOnMain(state: WalletState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
val isSaltPay = store.state.globalState.scanResponse?.cardTypesResolver?.isSaltPay() == true
|
||||
|
||||
when {
|
||||
isSaltPay && walletView !is SaltPayWalletView -> {
|
||||
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<W
|
|||
binding.toolbar.inflateMenu(R.menu.menu_wallet)
|
||||
}
|
||||
|
||||
setupCardImage(state, isSaltPay)
|
||||
setupCardImage(state)
|
||||
|
||||
if (!isSaltPay) showWarningsIfPresent(state.mainWarningsList)
|
||||
showWarningsIfPresent(state.mainWarningsList)
|
||||
|
||||
setupPullToRefreshLayout(state)
|
||||
|
||||
|
|
@ -255,24 +246,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), SafeStoreSubscriber<W
|
|||
binding.rvWarningMessages.show(warnings.isNotEmpty())
|
||||
}
|
||||
|
||||
private fun setupCardImage(state: WalletState, isSaltPay: Boolean) {
|
||||
// TODO: SaltPay: remove hardCode
|
||||
if (isSaltPay) {
|
||||
binding.ivCard.load(R.drawable.img_salt_pay_visa) {
|
||||
scale(Scale.FIT)
|
||||
crossfade(enable = true)
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
} else {
|
||||
binding.ivCard.load(state.cardImage?.artworkId) {
|
||||
scale(Scale.FIT)
|
||||
crossfade(enable = true)
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
private fun setupCardImage(state: WalletState) {
|
||||
binding.ivCard.load(state.cardImage?.artworkId) {
|
||||
scale(Scale.FIT)
|
||||
crossfade(enable = true)
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<Pair<String, MutableList<HistoryTransactionData>>>()
|
||||
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<HistoryItemData>()
|
||||
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<HistoryItemData>) =
|
||||
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<HistoryItemData>) =
|
||||
with(txWidget) {
|
||||
(rvTxHistory.adapter as TxHistoryAdapter).submitList(dataList)
|
||||
}
|
||||
|
||||
override fun onViewDestroy() {
|
||||
Timber.d("onViewDestroy")
|
||||
txWidget?.apply {
|
||||
rvTxHistoryShimmer.removeItemDecoration(itemDecorator)
|
||||
rvTxHistory.removeItemDecoration(itemDecorator)
|
||||
}
|
||||
super.onViewDestroy()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)}"
|
||||
}
|
||||
|
|
@ -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<HistoryItemData, BaseTxHistoryVH>(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<HistoryItemData>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,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
|
||||
|
|
@ -133,8 +132,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 -> {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UtorgCurrencyData, Blockchain?> = ChainToBlockchainConverter(),
|
||||
) : ExchangeService {
|
||||
|
||||
private val api = environment.utorgApi
|
||||
|
||||
private val utorgCurrencies = mutableListOf<UtorgCurrencyData>()
|
||||
|
||||
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<UtorgCurrencyData, Blockchain?> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Data> {
|
||||
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<Data> {
|
||||
val internalTimestamp = this.timestamp
|
||||
val internalData = this.data
|
||||
|
||||
return object : UtorgSuccessResponse<Data> {
|
||||
override val timestamp: Long = internalTimestamp
|
||||
override val data: Data = internalData!!
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(NullPointerException::class)
|
||||
fun toError(): UtorgErrorResponse = error!!
|
||||
}
|
||||
|
||||
interface UtorgSuccessResponse<T> {
|
||||
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,
|
||||
}
|
||||
|
|
@ -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<UtorgCurrencyData>?,
|
||||
@Json(name = "error") override val error: UtorgErrorResponse?,
|
||||
) : UtorgResponse<List<UtorgCurrencyData>>
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -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 <reified T> 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/button_disabled" android:state_enabled="false" />
|
||||
<item android:color="@color/button_secondary" />
|
||||
</selector>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="#66FFFFFF" android:state_enabled="false" />
|
||||
<item android:color="@color/text_primary_1" />
|
||||
</selector>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
|
|
@ -1,87 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_in_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="100dp"
|
||||
android:src="@drawable/ic_in_progress"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="@dimen/onboarding_wallet_top_guide_line_weight" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
style="@style/TextViewOnboarding.Header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_title_kyc_waiting"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_body"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/guideline1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
style="@style/TextViewOnboarding.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="55dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="55dp"
|
||||
android:text="@string/onboarding_subtitle_kyc_waiting"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_open_support_chat"
|
||||
style="@style/TapSecondaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="92dp"
|
||||
android:text="@string/chat_button_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/btn_container"
|
||||
app:layout_constraintStart_toStartOf="@id/btn_container" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_kyc_action"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_button_kyc_waiting" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="18dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/backgroundLightGray"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -221,13 +221,6 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/onboarding_saltpay_container"
|
||||
layout="@layout/layout_onboarding_saltpay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/onboarding_seed_phrase_container"
|
||||
layout="@layout/layout_onboarding_seed_phrase"
|
||||
|
|
@ -242,4 +235,4 @@
|
|||
layout="@layout/view_confetti"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
|||
|
|
@ -177,15 +177,6 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_salt_pay_wallet"
|
||||
layout="@layout/layout_salt_pay_wallet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintTop_toBottomOf="@id/rv_pending_transaction" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_address"
|
||||
layout="@layout/layout_address"
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_tx"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@drawable/ic_tx_incoming" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_hash"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="68dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:letterSpacing="0.01"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_sign"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="33BdfSkjandfjkajksjjkgkajsbga2B" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:letterSpacing="0.03"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash"
|
||||
tools:text="12:43" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:ellipsize="end"
|
||||
android:letterSpacing="0.03"
|
||||
android:singleLine="true"
|
||||
android:text="@string/transaction_history_tx_in_progress"
|
||||
android:textColor="@color/text_attention"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_sign"
|
||||
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_amount_sign"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:letterSpacing="0.02"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tv_tx_amount_value"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount_value"
|
||||
app:layout_constraintTop_toTopOf="@+id/tv_tx_amount_value"
|
||||
tools:text="+" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_amount_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:letterSpacing="0.02"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="443" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:letterSpacing="0.02"
|
||||
android:textColor="@color/text_tertiary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Today" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.skydoves.androidveil.VeilLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:veilLayout_baseColor="@color/lightGray0"
|
||||
app:veilLayout_highlightColor="@color/lightGray1"
|
||||
app:veilLayout_radius="4dp"
|
||||
app:veilLayout_shimmerEnable="true"
|
||||
app:veilLayout_veiled="true"
|
||||
tools:veilLayout_veiled="false">
|
||||
|
||||
<!--android:background="@drawable/rectangle_twin_background"-->
|
||||
<!--android:backgroundTint="@color/text_attention"-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/imv_tx"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_tx_hash"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="68dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tv_tx_amount"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_tx_time"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/tv_tx_hash" />
|
||||
|
||||
<View
|
||||
android:id="@+id/tv_tx_amount"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.skydoves.androidveil.VeilLayout>
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/onboarding_saltpay_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include
|
||||
android:id="@+id/pin_code"
|
||||
layout="@layout/layout_onboarding_saltpay_pin_code"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/connect_card"
|
||||
layout="@layout/layout_onboarding_saltpay_connect_card"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/verify_identity"
|
||||
layout="@layout/layout_onboarding_saltpay_kyc_start"
|
||||
android:visibility="gone" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webv_verify_identity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/kyc_in_progress"
|
||||
layout="@layout/layout_onboarding_saltpay_kyc_in_progress"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include
|
||||
android:id="@+id/claim"
|
||||
layout="@layout/layout_onboarding_main"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_connecting_socket"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_connecting_socket"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_header"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
style="@style/TextViewOnboarding.Header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:text="@string/onboarding_title_register_wallet"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_body"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imv_connecting_socket" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
style="@style/TextViewOnboarding.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="55dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="55dp"
|
||||
android:text="@string/onboarding_subtitle_register_wallet"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btn_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_connect"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_button_register_wallet"
|
||||
app:icon="@drawable/ic_tangem_24"
|
||||
app:iconGravity="textEnd" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="18dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/backgroundLightGray"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_in_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:src="@drawable/ic_in_progress"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
style="@style/TextViewOnboarding.Header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_title_kyc_waiting"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
style="@style/TextViewOnboarding.Body"
|
||||
android:layout_width="302dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header"
|
||||
tools:text="@string/onboarding_subtitle_kyc_waiting" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_open_support_chat"
|
||||
style="@style/TapSecondaryButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="92dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/btn_container"
|
||||
app:layout_constraintStart_toStartOf="@id/btn_container"
|
||||
tools:text="@string/chat_button_title" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_kyc_action"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_button_kyc_waiting" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="18dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/backgroundLightGray"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_passport"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_passport"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_header"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
style="@style/TextViewOnboarding.Header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:text="@string/onboarding_title_kyc_start"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_body"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imv_passport" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
style="@style/TextViewOnboarding.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="55dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="55dp"
|
||||
android:text="@string/onboarding_subtitle_kyc_start"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btn_container"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_verify"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_button_kyc_start" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="18dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/backgroundLightGray"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_header"
|
||||
style="@style/TextViewOnboarding.Header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="94dp"
|
||||
android:text="@string/onboarding_title_pin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_body"
|
||||
style="@style/TextViewOnboarding.Body"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="55dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="55dp"
|
||||
android:text="@string/onboarding_subtitle_pin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_header" />
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/fl_pin_code_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_body" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/btn_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_set_pin"
|
||||
style="@style/TapPrimaryButton"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_button_pin" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="18dp"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/backgroundLightGray"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="12dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/onboarding_balance_title"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.skydoves.androidveil.VeilLayout
|
||||
android:id="@+id/veil_balance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/tv_unreachable"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tv_title"
|
||||
app:veilLayout_baseColor="@color/lightGray0"
|
||||
app:veilLayout_highlightColor="@color/lightGray1"
|
||||
app:veilLayout_layout="@layout/card_total_balance_shimmer"
|
||||
app:veilLayout_radius="4dp"
|
||||
app:veilLayout_shimmerEnable="true"
|
||||
app:veilLayout_veiled="true"
|
||||
tools:veilLayout_veiled="false"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_balance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/text_primary_1"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="22 325.40 $" />
|
||||
|
||||
</com.skydoves.androidveil.VeilLayout>
|
||||
|
||||
<com.skydoves.androidveil.VeilLayout
|
||||
android:id="@+id/veil_balance_crypto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginTop="6dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/veil_balance"
|
||||
app:veilLayout_baseColor="@color/lightGray0"
|
||||
app:veilLayout_highlightColor="@color/lightGray1"
|
||||
app:veilLayout_layout="@layout/card_total_balance_shimmer"
|
||||
app:veilLayout_radius="4dp"
|
||||
app:veilLayout_shimmerEnable="true"
|
||||
app:veilLayout_veiled="true"
|
||||
tools:veilLayout_veiled="false"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_balance_crypto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:maxLines="1"
|
||||
android:minWidth="152dp"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="12sp"
|
||||
tools:text="5.13123123123 ETH"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</com.skydoves.androidveil.VeilLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_unreachable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/wallet_balance_blockchain_unreachable"
|
||||
android:textColor="@color/warning"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/veil_balance" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_buy"
|
||||
style="@style/BaseSaltPayButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="76dp"
|
||||
android:text="@string/wallet_button_buy"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_add"
|
||||
app:iconGravity="textStart"
|
||||
app:iconTint="@color/selector_saltpay_btn_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_currency_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:drawableEndCompat="@drawable/ic_arrow_angle_down"
|
||||
app:drawableTint="@color/text_tertiary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="USD" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card_balance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="12dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="272dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_tx_history_shimmer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="42dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/item_salt_pay_tx_history_shimmer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title_tx"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:letterSpacing="0.01"
|
||||
android:text="@string/transaction_history_title"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title_explore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:drawableLeft="@drawable/ic_tx_explore"
|
||||
android:drawablePadding="4dp"
|
||||
android:letterSpacing="0.01"
|
||||
android:text="Explore"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/imv_tx_list_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/tv_tx_list_empty"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:srcCompat="@drawable/ic_accept_coin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_list_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/transaction_history_empty_transactions"
|
||||
android:textColor="@color/text_tertiary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imv_tx_list_empty" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tx_list_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/transaction_history_error_failed_to_load"
|
||||
android:textColor="@color/text_tertiary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_tx_history"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="42dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/item_salt_pay_tx_history" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_shimmer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="rv_tx_history_shimmer" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="imv_tx_list_empty, tv_tx_list_empty" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="tv_tx_list_error" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/group_success"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="rv_tx_history" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/cl_salt_pay_wallet_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<include
|
||||
android:id="@+id/l_salt_pay_balance"
|
||||
layout="@layout/layout_salt_pay_balance"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
android:id="@+id/l_salt_pay_tx_history"
|
||||
layout="@layout/layout_salt_pay_tx_history"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/l_salt_pay_balance" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -97,19 +97,6 @@
|
|||
<item name="android:lineSpacingExtra">4sp</item>
|
||||
</style>
|
||||
|
||||
<style name="BaseSaltPayButton">
|
||||
<item name="android:minHeight">40dp</item>
|
||||
<item name="android:insetTop">0dp</item>
|
||||
<item name="android:insetBottom">0dp</item>
|
||||
<item name="android:elevation">0dp</item>
|
||||
<item name="android:textColor">@color/selector_saltpay_btn_text</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:backgroundTint">@color/selector_saltpay_btn</item>
|
||||
<item name="cornerRadius">@dimen/btn_corner_radius_medium</item>
|
||||
<item name="android:stateListAnimator">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="heading">
|
||||
<item name="android:textSize">24sp</item>
|
||||
<item name="android:textColor">#060606</item>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,5 @@ object ModuleErrorCode {
|
|||
const val COMMON = 10000
|
||||
const val NETWORK = 20000
|
||||
const val DOMAIN = 30000
|
||||
const val SALT_PAY = 40000
|
||||
const val ONBOARDING = 50000
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.datasource.config
|
|||
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.BlockchairCredentials
|
||||
import com.tangem.blockchain.common.BlockscoutCredentials
|
||||
import com.tangem.blockchain.common.GetBlockCredentials
|
||||
import com.tangem.blockchain.common.NowNodeCredentials
|
||||
import com.tangem.blockchain.common.QuickNodeCredentials
|
||||
|
|
@ -88,10 +87,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
authToken = configValues.blockchairAuthorizationToken,
|
||||
),
|
||||
blockcypherTokens = configValues.blockcypherTokens,
|
||||
blockscoutCredentials = BlockscoutCredentials(
|
||||
userName = configValues.saltPay.blockscoutCredentials.user,
|
||||
password = configValues.saltPay.blockscoutCredentials.password,
|
||||
),
|
||||
quickNodeSolanaCredentials = QuickNodeCredentials(
|
||||
apiKey = configValues.quiknodeApiKey,
|
||||
subdomain = configValues.quiknodeSubdomain,
|
||||
|
|
@ -102,7 +97,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
),
|
||||
infuraProjectId = configValues.infuraProjectId,
|
||||
tronGridApiKey = configValues.tronGridApiKey,
|
||||
saltPayAuthToken = configValues.saltPay.credentials.basicAuthToken,
|
||||
nowNodeCredentials = NowNodeCredentials(configValues.nowNodesApiKey),
|
||||
getBlockCredentials = GetBlockCredentials(configValues.getBlockApiKey),
|
||||
kaspaSecondaryApiUrl = configValues.kaspaSecondaryApiUrl,
|
||||
|
|
@ -115,7 +109,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
|
|||
amplitudeApiKey = configValues.amplitudeApiKey,
|
||||
shopify = configValues.shopifyShop,
|
||||
zendesk = configValues.zendesk,
|
||||
saltPayConfig = configValues.saltPay,
|
||||
swapReferrerAccount = configValues.swapReferrerAccount,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,5 @@ data class Config(
|
|||
val isCreatingTwinCardsAllowed: Boolean = false,
|
||||
val shopify: ShopifyShop? = null,
|
||||
val zendesk: ZendeskConfig? = null,
|
||||
val saltPayConfig: SaltPayConfig? = null,
|
||||
val swapReferrerAccount: SwapReferrerAccount? = null,
|
||||
)
|
||||
|
|
@ -7,7 +7,6 @@ import com.squareup.moshi.Json
|
|||
*/
|
||||
|
||||
class FeatureModel(
|
||||
val isWalletPayIdEnabled: Boolean,
|
||||
val isTopUpEnabled: Boolean,
|
||||
val isSendingToPayIdEnabled: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
|
|
@ -34,7 +33,6 @@ class ConfigValueModel(
|
|||
val appsFlyer: AppsFlyer,
|
||||
val shopifyShop: ShopifyShop?,
|
||||
val zendesk: ZendeskConfig?,
|
||||
val saltPay: SaltPayConfig,
|
||||
val tronGridApiKey: String,
|
||||
val amplitudeApiKey: String,
|
||||
val swapReferrerAccount: SwapReferrerAccount?,
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.datasource.config.models
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class SaltPayConfig(
|
||||
val sprinklr: SprinklrConfig,
|
||||
val kycProvider: KYCProvider,
|
||||
val credentials: Credentials,
|
||||
val blockscoutCredentials: Credentials,
|
||||
) {
|
||||
companion object {
|
||||
fun stub(): SaltPayConfig {
|
||||
return SaltPayConfig(
|
||||
sprinklr = SprinklrConfig("", ""),
|
||||
kycProvider = KYCProvider("", "", "", ""),
|
||||
credentials = Credentials("", ""),
|
||||
blockscoutCredentials = Credentials("", ""),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class KYCProvider(
|
||||
val baseUrl: String,
|
||||
val externalIdParameterKey: String,
|
||||
val sidParameterKey: String,
|
||||
val sidValue: String,
|
||||
)
|
||||
|
||||
data class Credentials(
|
||||
val user: String,
|
||||
val password: String,
|
||||
) {
|
||||
val basicAuthToken: String by lazy { okhttp3.Credentials.basic(user, password) }
|
||||
}
|
||||
|
|
@ -73,7 +73,6 @@ class ScanCardUseCase(
|
|||
private fun updateCardIdDisplayFormat(productType: ProductType) {
|
||||
tangemSdk.config.cardIdDisplayFormat = when (productType) {
|
||||
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
|
||||
ProductType.SaltPay -> CardIdDisplayFormat.None
|
||||
ProductType.Note,
|
||||
ProductType.Wallet,
|
||||
ProductType.Start2Coin,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ interface CardTypesResolver {
|
|||
fun isTangemNote(): Boolean
|
||||
fun isTangemWallet(): Boolean
|
||||
fun isWallet2(): Boolean
|
||||
fun isSaltPay(): Boolean
|
||||
fun isSaltPayVisa(): Boolean
|
||||
fun isSaltPayWallet(): Boolean
|
||||
fun isTangemTwins(): Boolean
|
||||
fun isStart2Coin(): Boolean
|
||||
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
package com.tangem.domain.common
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.common.CardIdRange
|
||||
import com.tangem.common.contains
|
||||
|
||||
object SaltPayWorkaround {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
val visaBatches = listOf(
|
||||
"AE02",
|
||||
"AE03",
|
||||
) + attachTestVisaBatches()
|
||||
|
||||
val walletCardIds = listOf(
|
||||
"AC01000000033503",
|
||||
"AC01000000033594",
|
||||
"AC01000000033586",
|
||||
"AC01000000034477",
|
||||
"AC01000000032760",
|
||||
"AC01000000033867",
|
||||
"AC01000000032653",
|
||||
"AC01000000032752",
|
||||
"AC01000000034485",
|
||||
"AC01000000033644",
|
||||
"AC01000000037454",
|
||||
"AC01000000037462",
|
||||
"AC03000000076070",
|
||||
"AC03000000076088",
|
||||
"AC03000000076096",
|
||||
"AC03000000076104",
|
||||
"AC03000000076112",
|
||||
"AC03000000076120",
|
||||
"AC03000000076138",
|
||||
"AC03000000076146",
|
||||
"AC03000000076153",
|
||||
"AC03000000076161",
|
||||
"AC03000000076179",
|
||||
"AC03000000076187",
|
||||
"AC03000000076195",
|
||||
"AC03000000076203",
|
||||
"AC03000000076211",
|
||||
"AC03000000076229",
|
||||
) + attachTestWalletCardIds()
|
||||
|
||||
val walletCardIdRanges = listOf(
|
||||
CardIdRange("AC05000000000003", "AC05000000015993")!!,
|
||||
) + attachTestWalletCardIdRanges()
|
||||
|
||||
fun tokenFrom(blockchain: Blockchain): Token {
|
||||
return when (blockchain) {
|
||||
Blockchain.SaltPay -> Token(
|
||||
name = "WXDAI",
|
||||
symbol = "wxDAI",
|
||||
contractAddress = "0x4200000000000000000000000000000000000006",
|
||||
decimals = 18,
|
||||
id = "wrapped-xdai",
|
||||
)
|
||||
else -> error("It is not SaltPay")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun isSaltPayCardId(cardId: String): Boolean = isVisaBatchId(cardId.take(4)) || isWalletCardId(cardId)
|
||||
|
||||
fun isVisaBatchId(batchId: String): Boolean = visaBatches.contains(batchId)
|
||||
|
||||
fun isWalletCardId(cardId: String): Boolean {
|
||||
return if (walletCardIds.contains(cardId)) true else walletCardIdRanges.contains(cardId)
|
||||
}
|
||||
|
||||
private fun attachTestVisaBatches(): List<String> = listOf(
|
||||
"FF03",
|
||||
)
|
||||
|
||||
private fun attachTestWalletCardIds(): List<String> = listOf(
|
||||
"FF04000000000232",
|
||||
)
|
||||
|
||||
private fun attachTestWalletCardIdRanges(): List<CardIdRange> = listOf(
|
||||
CardIdRange("FF04000000000000", "FF04999999999999")!!,
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue