Updated on 2026-08-14

This commit is contained in:
Tangem 2021-04-22 13:53:48 +03:00
parent e57d8ca630
commit 0571d8bb80
31 changed files with 507 additions and 289 deletions

View file

@ -75,10 +75,13 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:1.166.0'
implementation 'com.tangem:core:1.109.0'
implementation 'com.tangem:sdk:1.109.0'
// TODO: change to prod dependencies when they are available
// implementation 'com.tangem:blockchain:1.164.0'
// implementation 'com.tangem:core:1.109.0'
// implementation 'com.tangem:sdk:1.109.0'
implementation 'blockchain-sdk-kotlin:blockchain:tangem-20210416.151414-8'
implementation 'com.tangem:tangem-core:tangem-20210414.185224-2'
implementation 'com.tangem:tangem-sdk:tangem-20210414.185231-2'
// WebView
implementation "androidx.browser:browser:1.3.0"

View file

@ -25,16 +25,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
is GlobalAction.RestoreAppCurrency.Success -> {
globalState.copy(appCurrency = action.appCurrency)
}
is GlobalAction.UpdateWalletSignedHashes -> {
val card = globalState.scanNoteResponse?.card?.copy(
walletSignedHashes = action.walletSignedHashes
)
if (card != null) {
globalState.copy(scanNoteResponse = globalState.scanNoteResponse.copy(card = card))
} else {
globalState
}
}
is GlobalAction.SetConfigManager -> {
globalState.copy(configManager = action.configManager)
}

View file

@ -2,10 +2,16 @@ package com.tangem.tap.domain
import androidx.activity.ComponentActivity
import com.tangem.*
import com.tangem.commands.*
import com.tangem.commands.CommandResponse
import com.tangem.commands.PinType
import com.tangem.commands.SetPinCommand
import com.tangem.commands.SetPinResponse
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardType
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.commands.wallet.PurgeWalletResponse
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.extensions.calculateSha256
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tap.common.analytics.AnalyticsEvent
@ -32,13 +38,15 @@ class TangemSdkManager(val activity: ComponentActivity) {
initialMessage = Message(activity.getString(R.string.initial_message_scan_header)))
}
suspend fun createWallet(cardId: String?): CompletionResult<ScanNoteResponse> {
suspend fun createWallet(cardId: String?): CompletionResult<Card> {
return runTaskAsyncReturnOnMain(CreateWalletAndRescanTask(), cardId,
initialMessage = Message(activity.getString(R.string.initial_message_create_wallet_body)))
}
suspend fun eraseWallet(cardId: String?): CompletionResult<PurgeWalletResponse> {
return runTaskAsyncReturnOnMain(PurgeWalletCommand(), cardId,
return runTaskAsyncReturnOnMain(PurgeWalletCommand(
TangemSdkConstants.getDefaultWalletIndex()),
cardId,
initialMessage = Message(activity.getString(R.string.initial_message_purge_wallet_body)))
}

View file

@ -11,8 +11,7 @@ import com.tangem.tap.common.redux.global.FiatCurrencyName
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.extensions.amountToCreateAccount
import com.tangem.tap.domain.extensions.isNoAccountError
import com.tangem.tap.domain.extensions.*
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.domain.twins.TwinsHelper
@ -102,8 +101,10 @@ class TapWalletManager {
))
}
}
if (data.walletManager?.wallet?.blockchain == Blockchain.Ethereum ||
data.walletManager?.wallet?.blockchain == Blockchain.EthereumTestnet) {
val blockchain = data.card.getBlockchain()
if (blockchain == Blockchain.Ethereum ||
blockchain == Blockchain.EthereumTestnet) {
store.dispatch(TokensAction.LoadCardTokens)
}
loadData(data)
@ -112,10 +113,11 @@ class TapWalletManager {
private fun updateConfigManager(data: ScanNoteResponse) {
val configManager = store.state.globalState.configManager
val blockchain = data.card.getBlockchain()
if (TapWorkarounds.isStart2Coin) {
configManager?.turnOff(ConfigManager.isSendingToPayIdEnabled)
configManager?.turnOff(ConfigManager.isTopUpEnabled)
} else if (data.walletManager?.wallet?.blockchain == Blockchain.Bitcoin
} else if (blockchain == Blockchain.Bitcoin
|| data.card.cardData?.blockchainName == Blockchain.Bitcoin.id) {
configManager?.resetToDefault(ConfigManager.isSendingToPayIdEnabled)
configManager?.resetToDefault(ConfigManager.isTopUpEnabled)
@ -128,59 +130,77 @@ class TapWalletManager {
suspend fun loadData(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
val artworkId = data.verifyResponse?.artworkInfo?.id
if (data.walletManager != null) {
val config = store.state.globalState.configManager?.config ?: return@withContext
val primaryWalletManager = data.walletManager
val primaryBlockchain = data.walletManager.wallet.blockchain
val primaryTokenSymbol = data.card.cardData?.tokenSymbol
val primaryToken = primaryWalletManager.presetTokens.toList()
.firstOrNull { it.symbol == primaryTokenSymbol }
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(primaryBlockchain))
if (primaryToken != null) {
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
when {
data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, primaryBlockchain, primaryWalletManager)
} else {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
data.card.getStatus() == CardStatus.Empty ||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
store.dispatch(WalletAction.EmptyWallet)
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
else -> {
val config = store.state.globalState.configManager?.config ?: return@withContext
val blockchain = data.card.getBlockchain()
val primaryWalletManager = if (blockchain != null) {
walletManagerFactory.makeWalletManagerForApp(data.card, blockchain)
} else {
null
}
if (blockchain != null && primaryWalletManager != null) {
val primaryToken = data.card.getToken()
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
if (primaryToken != null) {
primaryWalletManager.addToken(primaryToken)
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, primaryWalletManager)
} else {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
}
} else {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, null)
}
}
store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
store.dispatch(WalletAction.LoadFiatRate())
}
store.dispatch(WalletAction.SetArtworkId(data.verifyResponse?.artworkInfo?.id))
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
store.dispatch(WalletAction.LoadFiatRate())
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
store.dispatch(WalletAction.EmptyWallet)
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
} else {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
store.dispatch(WalletAction.LoadArtwork(data.card, artworkId))
}
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
}
}
private fun loadMultiWalletData(
card: Card, primaryBlockchain: Blockchain, primaryWalletManager: WalletManager
card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
) {
val presetTokens = primaryWalletManager.presetTokens.toList()
val presetTokens = primaryWalletManager?.presetTokens?.toList() ?: emptyList()
val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
if (savedCurrencies == null) {
store.dispatch(WalletAction.MultiWallet.SaveCurrencies(
CardCurrencies(
blockchains = listOf(primaryBlockchain), tokens = presetTokens
)))
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens))
if (primaryBlockchain != null && primaryWalletManager != null) {
store.dispatch(WalletAction.MultiWallet.SaveCurrencies(
CardCurrencies(
blockchains = listOf(primaryBlockchain), tokens = presetTokens
)))
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(primaryBlockchain)))
store.dispatch(WalletAction.MultiWallet.AddTokens(presetTokens))
}
store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory))
store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
} else {
val blockchains = listOf(primaryBlockchain) + savedCurrencies.blockchains
val walletManagers = walletManagerFactory.makeWalletManagers(card, blockchains)
val blockchains = listOfNotNull(primaryBlockchain) + savedCurrencies.blockchains
val walletManagers = walletManagerFactory.makeWalletManagersForApp(card, blockchains)
val tokens = presetTokens + savedCurrencies.tokens
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
@ -192,18 +212,23 @@ class TapWalletManager {
suspend fun reloadData(data: ScanNoteResponse) {
withContext(Dispatchers.Main) {
if (data.walletManager != null) {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
return@withContext
when {
data.card.getBlockchain() == Blockchain.Unknown && !data.card.isMultiwalletAllowed -> {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
}
data.card.getStatus() == CardStatus.Empty ||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
store.dispatch(WalletAction.EmptyWallet)
}
else -> {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
store.dispatch(WalletAction.LoadData.Failure(TapError.NoInternetConnection))
return@withContext
}
val config = store.state.globalState.configManager?.config ?: return@withContext
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadFiatRate())
}
val config = store.state.globalState.configManager?.config ?: return@withContext
store.dispatch(WalletAction.LoadWallet(config.isTopUpEnabled))
store.dispatch(WalletAction.LoadFiatRate())
} else if (data.card.status == CardStatus.Empty || data.card.isTwinCard()) {
store.dispatch(WalletAction.EmptyWallet)
} else {
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
}
}
}

View file

@ -32,7 +32,7 @@ object TapWorkarounds {
"0030",
"0031",
"0035"
) // Tangem tags
)
private val excludedIssuers = listOf(
"TTM BANK"
@ -43,5 +43,6 @@ val Card.isMultiwalletAllowed: Boolean
get() {
return cardData?.productMask?.contains(Product.TwinCard) != true
&& !TapWorkarounds.isStart2Coin
&& this.curve == EllipticCurve.Secp256k1
&& (this.firmwareVersion.major >= 4 ||
this.getWallets().getOrNull(0)?.curve == EllipticCurve.Secp256k1)
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.EllipticCurve
import org.stellar.sdk.requests.ErrorResponse
import java.math.BigDecimal
@ -28,4 +29,15 @@ fun Blockchain.minimalAmount(): BigDecimal {
return 1.toBigDecimal().movePointLeft(decimals())
}
fun Blockchain.getCurve(): EllipticCurve? {
return when (this) {
Blockchain.Unknown -> null
Blockchain.Bitcoin, Blockchain.BitcoinTestnet, Blockchain.BitcoinCash, Blockchain.Litecoin,
Blockchain.Ducatus, Blockchain.Ethereum, Blockchain.EthereumTestnet, Blockchain.RSK,
Blockchain.Tezos, Blockchain.XRP, Blockchain.Binance, Blockchain.BinanceTestnet ->
EllipticCurve.Secp256k1
Blockchain.Cardano, Blockchain.CardanoShelley, Blockchain.Stellar -> EllipticCurve.Ed25519
}
}
private const val NODL = "NODL"

View file

@ -0,0 +1,44 @@
package com.tangem.tap.domain.extensions
import com.tangem.FirmwareConstraints
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.wallet.CardWallet
import com.tangem.commands.wallet.WalletStatus
import com.tangem.common.TangemSdkConstants
fun Card.getToken(): Token? {
val symbol = cardData?.tokenSymbol ?: return null
val contractAddress = cardData?.tokenContractAddress ?: return null
val decimals = cardData?.tokenDecimal ?: return null
return Token(symbol, contractAddress, decimals)
}
fun Card.getBlockchain(): Blockchain? {
val blockchainName: String = cardData?.blockchainName ?: return null
return Blockchain.fromId(blockchainName)
}
fun Card.getSingleWallet(): CardWallet? {
return wallet(TangemSdkConstants.getDefaultWalletIndex())
}
fun Card.getStatus(): CardStatus {
if (firmwareVersion < FirmwareConstraints.AvailabilityVersions.walletData) return status!!
return if (getWallets().any { it.status == WalletStatus.Loaded }) {
CardStatus.Loaded
} else {
CardStatus.Empty
}
}
fun Card.hasSignedHashes(): Boolean {
return getWallets().any { it.status == WalletStatus.Loaded && it.signedHashes ?: 0 > 0 }
}
fun Card.signedHashesCount(): Int {
return getWallets().map { it.signedHashes ?: 0 }.sum()
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.domain.extensions
import com.tangem.commands.wallet.WalletIndex
import com.tangem.common.TangemSdkConstants
fun TangemSdkConstants.Companion.getDefaultWalletIndex(): WalletIndex {
return WalletIndex.Index(oldCardDefaultWalletIndex)
}

View file

@ -0,0 +1,33 @@
package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
fun WalletManagerFactory.makeWalletManagerForApp(card: Card, blockchain: Blockchain): WalletManager? {
val curve = blockchain.getCurve() ?: return null
val publicKey = card.getWallets().firstOrNull { it.curve == curve }?.publicKey ?: return null
return makeWalletManager(card.cardId, publicKey, blockchain, curve)
}
fun WalletManagerFactory.makeWalletManagersForApp(
card: Card, blockchains: List<Blockchain>
): List<WalletManager> {
return makeWalletManagersForCurve(card, blockchains, EllipticCurve.Secp256k1) +
makeWalletManagersForCurve(card, blockchains, EllipticCurve.Ed25519)
}
fun WalletManagerFactory.makeWalletManagersForCurve(
card: Card, blockchains: List<Blockchain>, curve: EllipticCurve
): List<WalletManager> {
val blockchainsForCurve = blockchains.filter { it.getCurve() == curve }
val walletPublicKey = card.getWallets().firstOrNull { it.curve == curve }?.publicKey
return if (walletPublicKey != null) {
makeWalletManagers(card.cardId, walletPublicKey, blockchainsForCurve, curve)
} else {
emptyList()
}
}

View file

@ -2,26 +2,39 @@ package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.ReadCommand
import com.tangem.FirmwareConstraints
import com.tangem.TangemSdkError
import com.tangem.commands.common.card.Card
import com.tangem.common.CompletionResult
import com.tangem.tasks.CreateWalletTask
import com.tangem.tasks.PreflightReadCapable
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.PreflightReadTask
class CreateWalletAndRescanTask : CardSessionRunnable<ScanNoteResponse> {
class CreateWalletAndRescanTask : CardSessionRunnable<Card>, PreflightReadCapable {
override val requiresPin2 = false
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
CreateWalletTask().run(session) { result ->
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val firmwareVerion = session.environment.card?.firmwareVersion
if (firmwareVerion == null) {
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
}
val task = if (firmwareVerion < FirmwareConstraints.AvailabilityVersions.walletData) {
CreateWalletTask()
} else {
CreateWalletsTask()
}
task.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
ReadCommand().run(session) { readResult ->
when (readResult) {
is CompletionResult.Success -> ScanNoteTask(readResult.data).run(session, callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(readResult.error))
}
}
}
is CompletionResult.Success ->
PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}

View file

@ -0,0 +1,64 @@
package com.tangem.tap.domain.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.wallet.CreateWalletCommand
import com.tangem.commands.wallet.WalletConfig
import com.tangem.common.CompletionResult
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.PreflightReadTask
class CreateWalletsTask(wallets: List<WalletConfig> = emptyList()) : CardSessionRunnable<Card> {
override val requiresPin2 = false
val wallets = if (wallets.isEmpty()) {
listOf(
WalletConfig(null, null, EllipticCurve.Secp256k1, null),
WalletConfig(null, null, EllipticCurve.Ed25519, null),
WalletConfig(null, null, EllipticCurve.Secp256r1, null),
)
} else {
wallets
}
var index = 0
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
val walletConfig = wallets[index]
createWallet(walletConfig, session, callback)
}
private fun createWallet(
walletConfig: WalletConfig, session: CardSession,
callback: (result: CompletionResult<Card>) -> Unit
) {
CreateWalletCommand(
walletConfig, walletIndexValue = walletConfig.curveId!!.toWalletIndex()
).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
if (index == wallets.lastIndex) {
PreflightReadTask(PreflightReadSettings.FullCardRead).run(session, callback)
return@run
}
index += 1
createWallet(wallets[index], session, callback)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
}
private fun EllipticCurve.toWalletIndex(): Int {
return when (this) {
EllipticCurve.Secp256k1 -> 0
EllipticCurve.Ed25519 -> 1
EllipticCurve.Secp256r1 -> 2
}
}

View file

@ -1,36 +1,40 @@
package com.tangem.tap.domain.tasks
import com.tangem.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.commands.CommandResponse
import com.tangem.commands.ReadIssuerDataCommand
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.verifycard.VerifyCardCommand
import com.tangem.commands.verifycard.VerifyCardResponse
import com.tangem.commands.common.card.EllipticCurve
import com.tangem.commands.verification.VerifyCardCommand
import com.tangem.commands.verification.VerifyCardResponse
import com.tangem.commands.wallet.WalletConfig
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.extensions.getStatus
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.tap.store
import com.tangem.tasks.PreflightReadCapable
import com.tangem.tasks.PreflightReadSettings
import com.tangem.tasks.ScanTask
data class ScanNoteResponse(
val walletManager: WalletManager?,
val card: Card,
val verifyResponse: VerifyCardResponse? = null,
val secondTwinPublicKey: String? = null,
) : CommandResponse
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse> {
class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteResponse>, PreflightReadCapable {
override val requiresPin2 = false
override fun preflightReadSettings() = PreflightReadSettings.FullCardRead
override fun run(session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit) {
ScanTask().run(session) { result ->
when (result) {
@ -48,39 +52,25 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
return@run
}
if (card.isTwinCard()) {
dealWithTwinCard(card, session, callback)
return@run
}
val walletManager = try {
getWalletManagerFactory().makeWalletManager(card)
} catch (exception: Exception) {
if (card.isMultiwalletAllowed) {
// Create default Bitcoin WalletManager
getWalletManagerFactory().makeWalletManager(card, Blockchain.Bitcoin)
} else {
return@run callback(CompletionResult.Success(
ScanNoteResponse(null, card)
))
}
}
verifyCard(walletManager, card, null, session, callback)
verifyCard(card, session, callback)
}
}
}
}
private fun verifyCard(
walletManager: WalletManager?, card: Card, publicKey: String? = null,
session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
card: Card, session: CardSession, callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
) {
VerifyCardCommand(true).run(session) { verifyResult ->
when (verifyResult) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(ScanNoteResponse(
walletManager, card, verifyResult.data, publicKey)))
if (card.isTwinCard()) {
dealWithTwinCard(card, session, verifyResult.data, callback)
} else if (card.firmwareVersion.major >= 4) {
createMissingWalletsIfNeeded(card, session, verifyResult.data, callback)
} else {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResult.data)))
}
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
@ -89,39 +79,73 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
}
}
private fun createMissingWalletsIfNeeded(
card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
) {
if (card.getStatus() == CardStatus.Empty) {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
return
}
val curvesPresent = card.getWallets().map { it.curve }
val curvesToCreate = EllipticCurve.values().subtract(curvesPresent)
if (curvesToCreate.isEmpty()) {
callback(CompletionResult.Success(ScanNoteResponse(card, verifyResponse)))
return
}
val configs = curvesToCreate.map { curve ->
WalletConfig(
isReusable = null, prohibitPurgeWallet = null, curveId = curve,
signingMethods = null
)
}
CreateWalletsTask(configs).run(session) { result ->
when (result) {
is CompletionResult.Success ->
callback(CompletionResult.Success(ScanNoteResponse(result.data, verifyResponse)))
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
private fun dealWithTwinCard(
card: Card, session: CardSession,
card: Card, session: CardSession, verifyResponse: VerifyCardResponse,
callback: (result: CompletionResult<ScanNoteResponse>) -> Unit
) {
ReadIssuerDataCommand().run(session) { readDataResult ->
when (readDataResult) {
is CompletionResult.Success -> {
val publicKey = card.getSingleWallet()?.publicKey
if (publicKey == null) {
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
return@run
}
val verified = TwinCardsManager.verifyTwinPublicKey(
readDataResult.data.issuerData, card.walletPublicKey
readDataResult.data.issuerData, publicKey
)
if (verified) {
val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65)
val walletManager = try {
getWalletManagerFactory().makeMultisigWalletManager(card, twinPublicKey)
} catch (exception: Exception) {
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
return@run
}
verifyCard(walletManager, card, twinPublicKey.toHexString(), session, callback)
callback(CompletionResult.Success(
ScanNoteResponse(card, verifyResponse, twinPublicKey.toHexString())
))
return@run
} else {
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
}
}
is CompletionResult.Failure ->
callback(CompletionResult.Success(ScanNoteResponse(null, card)))
callback(CompletionResult.Success(ScanNoteResponse(card, null)))
}
}
}
private fun getErrorIfExcludedCard(card: Card): TangemError? {
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
if (card.status == CardStatus.Purged) return TangemSdkError.CardIsPurged()
if (card.status == CardStatus.Purged) return TangemSdkError.WalletIsPurged()
if (card.status == CardStatus.NotPersonalized) return TangemSdkError.NotPersonalized()
return null
}

View file

@ -7,6 +7,7 @@ import com.squareup.moshi.JsonClass
import com.squareup.moshi.Types
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.commands.common.card.FirmwareVersion
import com.tangem.tap.common.extensions.readJsonFileToString
import com.tangem.tap.network.createMoshi
@ -93,19 +94,27 @@ class CurrenciesRepository(val context: Application) {
return tokensAdapter.fromJson(json)!!.map { it.toToken() }
}
fun getBlockchains(): List<Blockchain> {
return listOf(
Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin,
Blockchain.XRP, Blockchain.Tezos,
Blockchain.Ethereum, Blockchain.RSK)
fun getBlockchains(cardFirmware: FirmwareVersion?): List<Blockchain> {
return if (cardFirmware == null || cardFirmware.major < 4) {
secp256k1Blochcains
} else {
secp256k1Blochcains + ed25519Blockchains
}
}
companion object {
private const val POPULAR_TOKENS_FILE_NAME = "erc20_tokens"
private const val FILE_NAME_PREFIX_TOKENS = "tokens"
private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains"
fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId"
fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId"
private val secp256k1Blochcains = listOf(
Blockchain.Bitcoin, Blockchain.BitcoinCash, Blockchain.Binance, Blockchain.Litecoin,
Blockchain.XRP, Blockchain.Tezos,
Blockchain.Ethereum, Blockchain.RSK)
private val ed25519Blockchains = listOf(Blockchain.CardanoShelley, Blockchain.Stellar)
}
}

View file

@ -2,18 +2,20 @@ package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.commands.CreateWalletResponse
import com.tangem.commands.PurgeWalletCommand
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.wallet.CreateWalletResponse
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tasks.CreateWalletTask
class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
override val requiresPin2 = false
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
if (session.environment.card?.walletPublicKey != null) {
PurgeWalletCommand().run(session) { response ->
if (session.environment.card?.getSingleWallet()?.publicKey != null) {
PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.copy(status = CardStatus.Empty)

View file

@ -3,11 +3,13 @@ package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.Message
import com.tangem.commands.CreateWalletResponse
import com.tangem.commands.PurgeWalletCommand
import com.tangem.commands.common.card.CardStatus
import com.tangem.commands.wallet.CreateWalletResponse
import com.tangem.commands.wallet.PurgeWalletCommand
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tasks.CreateWalletTask
class CreateSecondTwinWalletTask(
@ -18,9 +20,9 @@ class CreateSecondTwinWalletTask(
override val requiresPin2 = true
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
if (session.environment.card?.walletPublicKey != null) {
if (session.environment.card?.getSingleWallet()?.publicKey != null) {
session.setInitialMessage(preparingMessage)
PurgeWalletCommand().run(session) { response ->
PurgeWalletCommand(TangemSdkConstants.getDefaultWalletIndex()).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
session.environment.card =

View file

@ -3,7 +3,7 @@ package com.tangem.tap.domain.twins
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.KeyPair
import com.tangem.commands.ReadCommand
import com.tangem.commands.read.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.tasks.ScanNoteTask

View file

@ -6,6 +6,7 @@ import com.tangem.KeyPair
import com.tangem.TangemSdkError
import com.tangem.commands.*
import com.tangem.common.CompletionResult
import com.tangem.common.TangemSdkConstants
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.guard
import com.tangem.common.files.FileHashHelper
@ -17,14 +18,14 @@ class WriteProtectedIssuerDataTask(
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val hashes = arrayOf(twinPublicKey.calculateSha256())
SignCommand(hashes).run(session) { signResult ->
SignCommand(hashes, TangemSdkConstants.getDefaultWalletIndex()).run(session) { signResult ->
when (signResult) {
is CompletionResult.Success -> {
ReadIssuerDataCommand().run(session) { readResult ->
when (readResult) {
is CompletionResult.Success -> {
writeIssuerData(
twinPublicKey, issuerKeys, signResult.data.signature,
twinPublicKey, issuerKeys, signResult.data.signatures[0],
readResult.data, session, callback
)
}

View file

@ -203,6 +203,5 @@ private fun prepareAllowedSecurityOptions(card: Card): EnumSet<SecurityOption> {
private fun Card.toCardInfo(): CardInfo? {
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
val issuer = this.cardData?.issuerName ?: return null
val signedHashes = this.walletSignedHashes ?: return null
return CardInfo(cardId, issuer, signedHashes)
return CardInfo(cardId, issuer)
}

View file

@ -26,7 +26,6 @@ data class DetailsState(
data class CardInfo(
val cardId: String,
val issuer: String,
val signedHashes: Int,
)
enum class EraseWalletState { Allowed, NotAllowedByCard, NotEmpty }

View file

@ -18,7 +18,7 @@ class CreateTwinWalletMiddleware {
fun handle(action: DetailsAction.CreateTwinWalletAction) {
when (action) {
is DetailsAction.CreateTwinWalletAction.ShowWarning -> {
val wallet = store.state.globalState.scanNoteResponse?.walletManager?.wallet
val wallet = store.state.detailsState.wallets.firstOrNull()
if (wallet == null) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.CreateTwinWalletWarning))
return

View file

@ -74,12 +74,8 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
}
tv_card_id.text = cardId
tv_issuer.text = state.cardInfo.issuer
tv_signed_hashes.text = state.cardInfo.signedHashes.toString()
}
tv_signed_hashes.show(state.card?.isTwinCard() != true)
tv_signed_hashes_title.show(state.card?.isTwinCard() != true)
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
tv_card_tou.show(state.cardTermsOfUseUrl != null)

View file

@ -18,6 +18,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.store
import timber.log.Timber
import java.io.File
@ -161,7 +162,7 @@ class AdditionalEmailInfo {
fun setCardInfo(card: Card) {
cardId = card.cardId
cardFirmwareVersion = card.firmwareVersion.version
signedHashesCount = card.walletSignedHashes?.toString() ?: "0"
signedHashesCount = card.signedHashesCount().toString()
}
fun setWalletsInfo(wallets: List<Wallet>) {
@ -274,7 +275,6 @@ class FeedbackEmail : EmailData {
val builder = StringBuilder()
builder.appendKeyValue("Card ID", infoHolder.cardId)
builder.appendKeyValue("Firmware version", infoHolder.cardFirmwareVersion)
builder.appendKeyValue("Signed hashes", infoHolder.signedHashesCount)
infoHolder.walletsInfo.forEach {
builder.appendKeyValue("Blockchain", it.blockchain.fullName)

View file

@ -4,14 +4,13 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.Signer
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.commands.common.card.Card
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds
@ -115,11 +114,10 @@ private fun sendTransaction(
val result = (walletManager as TransactionSender).send(txData, signer)
withContext(Dispatchers.Main) {
when (result) {
is Result.Success -> {
is SimpleResult.Success -> {
tangemSdk.config.linkedTerminal = isLinkedTerminal
FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.TRANSACTION_IS_SENT, card)
dispatch(SendAction.SendSuccess)
dispatch(GlobalAction.UpdateWalletSignedHashes(result.data.walletSignedHashes))
dispatch(NavigationAction.PopBackTo())
scope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
@ -131,7 +129,7 @@ private fun sendTransaction(
}
}
}
is Result.Failure -> {
is SimpleResult.Failure -> {
when (result.error) {
is CreateAccountUnderfunded -> {
val error = result.error as CreateAccountUnderfunded

View file

@ -5,7 +5,6 @@ import com.tangem.tap.common.CurrencyConverter
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.states.IdStateHolder
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.store
import org.rekotlin.Action
import java.math.BigDecimal
@ -60,8 +59,7 @@ private class EmptyReducer : SendInternalReducer {
private class PrepareSendScreenStatesReducer : SendInternalReducer {
override fun handle(action: SendScreenAction, sendState: SendState): SendState {
val prepareAction = action as PrepareSendScreen
val walletManager = action.walletManager
?: store.state.globalState.scanNoteResponse!!.walletManager!!
val walletManager = action.walletManager!!
val amountToExtract = prepareAction.tokenAmount ?: prepareAction.coinAmount!!
val decimals = amountToExtract.decimals

View file

@ -13,8 +13,9 @@ class TokensMiddleware {
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> {
val cardFirmware = state()?.globalState?.scanNoteResponse?.card?.firmwareVersion
val tokens = currenciesRepository.getPopularTokens()
val blockchains = currenciesRepository.getBlockchains()
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
val currencies = CurrencyListItem.createListOfCurrencies(
blockchains, tokens
)

View file

@ -10,6 +10,8 @@ import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.extensions.makeWalletManagersForApp
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.scope
@ -46,7 +48,7 @@ class MultiWalletMiddleware {
globalState?.scanNoteResponse?.card?.let { card ->
currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain)
globalState.tapWalletManager.walletManagerFactory
.makeWalletManager(card, action.blockchain)?.let {
.makeWalletManagerForApp(card, action.blockchain)?.let {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
}
}
@ -66,9 +68,10 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
val blockchains = currenciesRepository.getBlockchains()
val cardFirmware = globalState?.scanNoteResponse?.card?.firmwareVersion
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
.filterNot { walletState?.blockchains?.contains(it) == true }
val walletManagers = action.factory.makeWalletManagers(action.card, blockchains)
val walletManagers = action.factory.makeWalletManagersForApp(action.card, blockchains)
scope.launch {
walletManagers.map { walletManager ->

View file

@ -108,7 +108,11 @@ class WalletMiddleware {
)
when (result) {
is CompletionResult.Success -> {
globalState?.tapWalletManager?.onCardScanned(result.data)
val scanNoteResponse =
globalState?.scanNoteResponse?.copy(card = result.data)
scanNoteResponse?.let {
globalState.tapWalletManager.onCardScanned(scanNoteResponse)
}
}
}
}

View file

@ -6,7 +6,7 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.commands.common.card.Card
import com.tangem.commands.common.card.CardType
import com.tangem.commands.verifycard.VerifyCardState
import com.tangem.commands.verification.VerifyCardState
import com.tangem.common.extensions.getType
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
@ -14,6 +14,8 @@ import com.tangem.tap.common.extensions.isGreaterThan
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.extensions.hasSignedHashes
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.twins.isTwinCard
import com.tangem.tap.features.wallet.redux.WalletAction
@ -63,13 +65,12 @@ class WarningsMiddleware {
}
private fun showCardWarningsIfNeeded(globalState: GlobalState?) {
val validator = globalState?.scanNoteResponse?.walletManager as? SignatureCountValidator
globalState?.scanNoteResponse?.card?.let { card ->
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
if (card.getType() != CardType.Release) {
addWarningMessage(WarningMessagesManager.devCardWarning())
} else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
checkIfWarningNeeded(card, validator)?.let { addWarningMessage(it) }
checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) }
}
if (card.getType() == CardType.Release) {
if (globalState.scanNoteResponse.verifyResponse?.verificationState ==
@ -82,12 +83,14 @@ class WarningsMiddleware {
}
private fun checkIfWarningNeeded(
card: Card, signatureCountValidator: SignatureCountValidator? = null,
card: Card
): WarningMessage? {
if (card.isTwinCard() || card.isMultiwalletAllowed) return null
if (card.isTwinCard()) return null
return if (signatureCountValidator == null) {
if (card.walletSignedHashes ?: 0 > 0) {
val validator = store.state.walletState.walletManagers.firstOrNull()
as? SignatureCountValidator
return if (validator == null || card.isMultiwalletAllowed) {
if (card.hasSignedHashes()) {
WarningMessagesManager.alreadySignedHashesWarning()
} else {
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
@ -108,10 +111,11 @@ class WarningsMiddleware {
if (card.isTwinCard() || card.isMultiwalletAllowed) return
val validator = store.state.globalState.scanNoteResponse?.walletManager
val validator = store.state.walletState.walletManagers.firstOrNull()
as? SignatureCountValidator
scope.launch {
val result = validator?.validateSignatureCount(card.walletSignedHashes ?: 0)
val signedHashes = card.getSingleWallet()?.signedHashes ?: 0
val result = validator?.validateSignatureCount(signedHashes)
withContext(Dispatchers.Main) {
when (result) {
SimpleResult.Success -> {
@ -121,7 +125,7 @@ class WarningsMiddleware {
is SimpleResult.Failure ->
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
} else if (card.walletSignedHashes ?: 0 > 0) {
} else if (signedHashes > 0) {
addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
}
}

View file

@ -34,28 +34,26 @@ class MultiWalletView : WalletView {
override fun changeWalletView(fragment: WalletFragment) {
setFragment(fragment)
onViewCreated()
showMultiWalletView()
setupButtons()
showMultiWalletView(fragment)
setupButtons(fragment)
}
private fun showMultiWalletView() {
val fragment = fragment ?: return
fragment.tv_twin_card_number.hide()
fragment.iv_twin_card.hide()
fragment.rv_pending_transaction.hide()
fragment.l_card_balance.hide()
fragment.l_address.hide()
fragment.l_buttons_short.hide()
fragment.l_buttons_long.hide()
fragment.btn_scan_multiwallet?.show()
fragment.rv_multiwallet.show()
fragment.btn_add_token.show()
private fun showMultiWalletView(fragment: WalletFragment) = with(fragment) {
tv_twin_card_number.hide()
iv_twin_card.hide()
rv_pending_transaction.hide()
l_card_balance.hide()
l_address.hide()
l_buttons_short.hide()
l_buttons_long.hide()
btn_scan_multiwallet?.show()
rv_multiwallet.show()
btn_add_token.show()
}
private fun setupButtons() {
val fragment = fragment ?: return
fragment.btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
private fun setupButtons(fragment: WalletFragment) = with(fragment) {
btn_scan_multiwallet?.setOnClickListener { store.dispatch(WalletAction.Scan) }
}
override fun setFragment(fragment: WalletFragment) {
@ -114,22 +112,22 @@ class MultiWalletView : WalletView {
private fun showErrorState(
fragment: WalletFragment, errorTitle: CharSequence, errorDescription: CharSequence,
) {
fragment.l_card_balance.show()
fragment.l_balance.hide()
fragment.l_balance_error.show()
fragment.rv_multiwallet.hide()
fragment.btn_add_token.hide()
fragment.tv_error_title.text = errorTitle
fragment.tv_error_descriptions.text = errorDescription
) = with(fragment) {
l_card_balance.show()
l_balance.hide()
l_balance_error.show()
rv_multiwallet.hide()
btn_add_token.hide()
tv_error_title.text = errorTitle
tv_error_descriptions.text = errorDescription
}
private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) {
fragment.btn_scan_multiwallet.hide()
fragment.l_buttons_long.show()
fragment.btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
fragment.btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
fragment.btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
private fun configureButtonsForEmptyWalletState(fragment: WalletFragment) = with(fragment) {
btn_scan_multiwallet.hide()
l_buttons_long.show()
btn_scan_long.setOnClickListener { store.dispatch(WalletAction.Scan) }
btn_confirm_long.setOnClickListener { store.dispatch(WalletAction.CreateWallet) }
btn_confirm_long.text = fragment.getText(R.string.wallet_button_create_wallet)
}
private fun handleDialogs(walletDialog: StateDialog?) {

View file

@ -44,17 +44,16 @@ class SingleWalletView : WalletView {
override fun changeWalletView(fragment: WalletFragment) {
setFragment(fragment)
onViewCreated()
showSingleWalletView()
showSingleWalletView(fragment)
}
private fun showSingleWalletView() {
val fragment = fragment ?: return
fragment.rv_multiwallet.hide()
fragment.btn_add_token.hide()
fragment.btn_scan_multiwallet?.hide()
fragment.rv_pending_transaction.hide()
fragment.l_card_balance.show()
fragment.l_address.show()
private fun showSingleWalletView(fragment: WalletFragment) = with(fragment) {
rv_multiwallet.hide()
btn_add_token.hide()
btn_scan_multiwallet?.hide()
rv_pending_transaction.hide()
l_card_balance.show()
l_address.show()
}
override fun onViewCreated() {
@ -70,13 +69,15 @@ class SingleWalletView : WalletView {
}
override fun onNewState(state: WalletState) {
setupTwinCards(state.twinCardsState)
val fragment = fragment ?: return
state.primaryWallet ?: return
setupButtons(state.primaryWallet, state.twinCardsState != null)
setupAddressCard(state.primaryWallet)
setupTwinCards(state.twinCardsState, fragment)
setupButtons(state.primaryWallet, state.twinCardsState != null, fragment)
setupAddressCard(state.primaryWallet, fragment)
showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions)
setupBalance(state, state.primaryWallet)
handleDialogs(state.walletDialog)
handleDialogs(state.walletDialog, fragment)
}
private fun showPendingTransactionsIfPresent(pendingTransactions: List<PendingTransaction>) {
@ -92,43 +93,45 @@ class SingleWalletView : WalletView {
}
}
private fun setupTwinCards(twinCardsState: TwinCardsState?) {
fragment?.apply {
twinCardsState?.cardNumber?.let { cardNumber ->
this.tv_twin_card_number.show()
this.iv_twin_card.show()
val number = when (cardNumber) {
TwinCardNumber.First -> "1"
TwinCardNumber.Second -> "2"
}
this.tv_twin_card_number.text =
this.getString(R.string.wallet_twins_chip_format, number)
}
if (twinCardsState?.cardNumber == null) {
this.tv_twin_card_number.hide()
this.iv_twin_card.hide()
}
if (twinCardsState?.showTwinOnboarding == true) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
private fun setupTwinCards(
twinCardsState: TwinCardsState?, fragment: WalletFragment
) = with(fragment) {
twinCardsState?.cardNumber?.let { cardNumber ->
tv_twin_card_number.show()
iv_twin_card.show()
val number = when (cardNumber) {
TwinCardNumber.First -> "1"
TwinCardNumber.Second -> "2"
}
tv_twin_card_number.text =
this.getString(R.string.wallet_twins_chip_format, number)
}
if (twinCardsState?.cardNumber == null) {
tv_twin_card_number.hide()
iv_twin_card.hide()
}
if (twinCardsState?.showTwinOnboarding == true) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
}
}
private fun setupButtons(state: WalletData, isTwinsWallet: Boolean) {
val fragment = fragment ?: return
private fun setupButtons(
state: WalletData, isTwinsWallet: Boolean, fragment: WalletFragment
) = with(fragment){
setupButtonsType(state, fragment)
val btnConfirm = if (state.topUpState.allowed) {
fragment.btn_confirm_short
btn_confirm_short
} else {
fragment.btn_confirm_long
btn_confirm_long
}
val btnScan = if (state.topUpState.allowed) {
fragment.btn_scan_short
btn_scan_short
} else {
fragment.btn_scan_long
btn_scan_long
}
setupConfirmButton(state, btnConfirm, fragment, isTwinsWallet)
@ -137,27 +140,27 @@ class SingleWalletView : WalletView {
store.dispatch(WalletAction.Scan)
}
fragment.btn_copy.setOnClickListener {
btn_copy.setOnClickListener {
state.walletAddresses?.selectedAddress?.address?.let { addressString ->
store.dispatch(WalletAction.CopyAddress(addressString, fragment.requireContext()))
}
}
fragment.btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
btn_show_qr.setOnClickListener { store.dispatch(WalletAction.ShowDialog.QrCode) }
fragment.btn_top_up.setOnClickListener {
btn_top_up.setOnClickListener {
store.dispatch(
WalletAction.TopUpAction.TopUp(fragment.requireContext(), R.color.backgroundLightGray)
)
}
}
private fun setupButtonsType(state: WalletData, fragment: WalletFragment) {
private fun setupButtonsType(state: WalletData, fragment: WalletFragment) = with(fragment) {
if (state.topUpState.allowed) {
fragment.l_buttons_long.hide()
fragment.l_buttons_short.show()
l_buttons_long.hide()
l_buttons_short.show()
} else {
fragment.l_buttons_long.show()
fragment.l_buttons_short.hide()
l_buttons_long.show()
l_buttons_short.hide()
}
}
@ -185,39 +188,37 @@ class SingleWalletView : WalletView {
}
private fun setupAddressCard(state: WalletData) {
val fragment = fragment ?: return
private fun setupAddressCard(state: WalletData, fragment: WalletFragment) = with(fragment) {
if (state.walletAddresses != null && state.blockchain != null) {
fragment.l_address?.show()
l_address?.show()
if (state.shouldShowMultipleAddress()) {
(fragment.l_address as? ViewGroup)?.beginDelayedTransition()
fragment.chip_group_address_type.show()
fragment.chip_group_address_type.fitChipsByGroupWidth()
(l_address as? ViewGroup)?.beginDelayedTransition()
chip_group_address_type.show()
chip_group_address_type.fitChipsByGroupWidth()
val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type)
if (checkedId != View.NO_ID) fragment.chip_group_address_type.check(checkedId)
if (checkedId != View.NO_ID) chip_group_address_type.check(checkedId)
fragment.chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
chip_group_address_type.setOnCheckedChangeListener { group, checkedId ->
if (checkedId == -1) return@setOnCheckedChangeListener
val type = MultipleAddressUiHelper.idToType(checkedId, state.blockchain)
type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) }
}
} else {
fragment.chip_group_address_type.hide()
chip_group_address_type.hide()
}
fragment.tv_address.text = state.walletAddresses.selectedAddress.address
fragment.tv_explore?.setOnClickListener {
tv_address.text = state.walletAddresses.selectedAddress.address
tv_explore?.setOnClickListener {
store.dispatch(WalletAction.ExploreAddress(
state.walletAddresses.selectedAddress.exploreUrl,
fragment.requireContext()))
}
} else {
fragment.l_address?.hide()
l_address?.hide()
}
}
private fun handleDialogs(walletDialog: StateDialog?) {
val fragment = fragment ?: return
private fun handleDialogs(walletDialog: StateDialog?, fragment: WalletFragment) {
val context = fragment.context ?: return
when (walletDialog) {
is WalletDialog.QrDialog -> {

View file

@ -90,28 +90,6 @@
app:layout_constraintTop_toBottomOf="@id/tv_card_id"
tools:text="Tangem" />
<TextView
android:id="@+id/tv_signed_hashes_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="7dp"
android:paddingBottom="7dp"
android:text="@string/details_row_title_signed_hashes"
android:textColor="@color/darkGray6"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_issuer_title" />
<TextView
android:id="@+id/tv_signed_hashes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/darkGray1"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_issuer"
tools:text="48 hashes" />
<TextView
android:id="@+id/tv_disclaimer"
android:layout_width="match_parent"
@ -124,7 +102,7 @@
android:textSize="16sp"
app:drawableEndCompat="@drawable/ic_angle_bracket_right"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_signed_hashes_title" />
app:layout_constraintTop_toBottomOf="@id/tv_issuer_title" />
<TextView
android:id="@+id/tv_card_tou"