Updated on 2026-08-14
This commit is contained in:
parent
25e8887043
commit
5eb8b71a1f
21 changed files with 316 additions and 160 deletions
|
|
@ -77,8 +77,8 @@ dependencies {
|
|||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation 'com.tangem:blockchain:develop-38'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-70'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-70'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-78'
|
||||
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-78'
|
||||
|
||||
// WebView
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
|
|
|
|||
|
|
@ -14,4 +14,11 @@ fun <T> MutableList<T>.removeBy(predicate: (T) -> Boolean): Boolean {
|
|||
val toRemove = this.filter(predicate)
|
||||
this.removeAll(toRemove)
|
||||
return toRemove.isNotEmpty()
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.replaceBy(item: T, predicate: (T) -> Boolean) {
|
||||
val toRemove = this.filter(predicate)
|
||||
val indexes = toRemove.map { indexOf(it) }
|
||||
this.removeAll(toRemove)
|
||||
indexes.forEach { this.add(it, item) }
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.tap.domain.MultiMessageError
|
|||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.assembleErrors
|
||||
import com.tangem.tap.notificationsHandler
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import java.lang.ref.WeakReference
|
||||
|
|
@ -63,23 +64,29 @@ fun getMessageString(context: Context, message: Int, args: List<Any>?): String {
|
|||
val notificationsMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is NotificationAction -> notificationsHandler?.showNotification(action.messageResource)
|
||||
is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
|
||||
is ErrorAction -> {
|
||||
when (action.error) {
|
||||
is MultiMessageError -> {
|
||||
val multiError = action.error as MultiMessageError
|
||||
notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
|
||||
}
|
||||
else -> {
|
||||
val args = (action.error as? ArgError)?.args ?: listOf()
|
||||
notificationsHandler?.showNotification(action.error.localizedMessage, args)
|
||||
}
|
||||
}
|
||||
handleNotificationAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNotificationAction(action: Action) {
|
||||
if (action is Debug && !BuildConfig.DEBUG) return
|
||||
|
||||
when (action) {
|
||||
is NotificationAction -> notificationsHandler?.showNotification(action.messageResource)
|
||||
is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
|
||||
is ErrorAction -> {
|
||||
when (action.error) {
|
||||
is MultiMessageError -> {
|
||||
val multiError = action.error as MultiMessageError
|
||||
notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
|
||||
}
|
||||
else -> {
|
||||
val args = (action.error as? ArgError)?.args ?: listOf()
|
||||
notificationsHandler?.showNotification(action.error.localizedMessage, args)
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,4 +101,10 @@ interface NotificationAction : Action {
|
|||
|
||||
interface ErrorAction : Action {
|
||||
val error: TapError
|
||||
}
|
||||
}
|
||||
|
||||
// Processed only in the debug builds
|
||||
interface Debug
|
||||
interface DebugNotification : Debug, NotificationAction
|
||||
interface DebugToastNotification : Debug, ToastNotificationAction
|
||||
interface DebugErrorAction : Debug, ErrorAction
|
||||
|
|
@ -22,7 +22,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
|
|||
import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.tasks.ScanNoteTask
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -35,16 +35,22 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
analyticsHandler: AnalyticsHandler, messageRes: Int? = null,
|
||||
): CompletionResult<ScanNoteResponse> {
|
||||
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
|
||||
val result = runTaskAsyncReturnOnMain(ScanNoteTask(),
|
||||
initialMessage = Message(
|
||||
context.getString(messageRes ?: R.string.initial_message_scan_header)
|
||||
))
|
||||
if (result is CompletionResult.Failure) {
|
||||
|
||||
return runTaskAsyncReturnOnMain(
|
||||
ScanNoteTask(),
|
||||
initialMessage = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
).also { sendScanFailuresToAnalytics(analyticsHandler, it) }
|
||||
}
|
||||
|
||||
private fun sendScanFailuresToAnalytics(
|
||||
analyticsHandler: AnalyticsHandler,
|
||||
result: CompletionResult<ScanNoteResponse>
|
||||
) {
|
||||
if (result is CompletionResult.Failure && result.error is TangemSdkError) {
|
||||
(result.error as? TangemSdkError)?.let { error ->
|
||||
analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun createWallet(cardId: String?): CompletionResult<Card> {
|
||||
|
|
@ -106,7 +112,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
}
|
||||
|
||||
fun changeDisplayedCardIdNumbersCount(card: Card) {
|
||||
tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTwinCard()) 4 else null
|
||||
tangemSdk.config.cardIdDisplayedNumbersCount = if (card.isTangemTwin()) 4 else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
class TapBlockchainConfig {
|
||||
companion object {
|
||||
val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT"
|
||||
val blockcypherTokens: Set<String> = setOf(
|
||||
"aa8184b0e0894b88a5688e01b3dc1e82",
|
||||
"56c4ca23c6484c8f8864c32fde4def8d",
|
||||
"66a8a37c5e9d4d2c9bb191acfe7f93aa"
|
||||
)
|
||||
val infuraProjectId: String = "613a0b14833145968b1f656240c7d245"
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ sealed class TapError(
|
|||
) : Throwable(), TapErrors, ArgError {
|
||||
|
||||
object UnknownError : TapError(R.string.send_error_unknown)
|
||||
object ScanCardError : TapError(R.string.scan_card_error)
|
||||
data class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
|
||||
object PayIdAlreadyCreated : TapError(R.string.wallet_create_payid_error_already_created)
|
||||
object PayIdCreatingError : TapError(R.string.wallet_create_payid_error_message)
|
||||
|
|
@ -38,8 +39,8 @@ sealed class TapError(
|
|||
object DustChange : TapError(R.string.send_error_dust_change)
|
||||
data class CreateAccountUnderfunded(override val args: List<Any>) : TapError(R.string.send_error_no_target_account)
|
||||
|
||||
sealed class XmlError {
|
||||
object AssetAccountNotCreated: TapError(R.string.send_error_no_account_xlm)
|
||||
sealed class XmlError {
|
||||
object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm)
|
||||
}
|
||||
|
||||
data class ValidateTransactionErrors(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ 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
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.tap.features.tokens.redux.TokensAction
|
||||
import com.tangem.tap.features.wallet.redux.Currency
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
|
|
@ -98,7 +98,7 @@ class TapWalletManager {
|
|||
store.dispatch(GlobalAction.SaveScanNoteResponse(data))
|
||||
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
|
||||
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
|
||||
if (data.card.isTwinCard()) {
|
||||
if (data.card.isTangemTwin()) {
|
||||
val secondCardId = TwinsHelper.getTwinsCardId(data.card.cardId)
|
||||
val cardNumber = TwinsHelper.getTwinCardNumber(data.card.cardId)
|
||||
if (secondCardId != null && cardNumber != null) {
|
||||
|
|
@ -143,7 +143,7 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
|
||||
}
|
||||
data.card.wallets.isEmpty() ||
|
||||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
|
||||
(data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> {
|
||||
store.dispatch(WalletAction.EmptyWallet)
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -152,7 +152,7 @@ class TapWalletManager {
|
|||
val blockchain = data.getBlockchain()
|
||||
val primaryWalletManager = walletManagerFactory.makePrimaryWalletManager(data)
|
||||
|
||||
if (blockchain != null && primaryWalletManager != null) {
|
||||
if (blockchain != Blockchain.Unknown && primaryWalletManager != null) {
|
||||
val primaryToken = data.getPrimaryToken()
|
||||
|
||||
store.dispatch(WalletAction.MultiWallet.SetPrimaryBlockchain(blockchain))
|
||||
|
|
@ -236,7 +236,7 @@ class TapWalletManager {
|
|||
store.dispatch(WalletAction.LoadData.Failure(TapError.UnknownBlockchain))
|
||||
}
|
||||
data.card.wallets.isEmpty() ||
|
||||
(data.card.isTwinCard() && data.secondTwinPublicKey == null) -> {
|
||||
(data.card.isTangemTwin() && data.secondTwinPublicKey == null) -> {
|
||||
store.dispatch(WalletAction.EmptyWallet)
|
||||
}
|
||||
else -> {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.tap.domain.TapWorkarounds.isNote
|
||||
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import java.util.*
|
||||
|
||||
object TapWorkarounds {
|
||||
|
|
@ -28,26 +28,10 @@ object TapWorkarounds {
|
|||
return excludedBatch || excludedIssuerName
|
||||
}
|
||||
|
||||
fun Card.isNote(): Boolean {
|
||||
return notesBatches.contains(batchId)
|
||||
}
|
||||
fun Card.isTangemNote(): Boolean = tangemNoteBatches.contains(batchId)
|
||||
fun Card.isTangemWallet(): Boolean = tangemWalletBatches.contains(batchId)
|
||||
|
||||
fun Card.isMultiCurrencyWallet(): Boolean {
|
||||
return multiCurrencyWalletsBatches.contains(batchId)
|
||||
}
|
||||
|
||||
val Card.noteCurrency: Blockchain?
|
||||
get() {
|
||||
return when (batchId) {
|
||||
"AB01" -> Blockchain.Bitcoin
|
||||
"AB02" -> Blockchain.Ethereum
|
||||
"AB03" -> Blockchain.CardanoShelley
|
||||
"AB04" -> Blockchain.Dogecoin
|
||||
"AB05" -> Blockchain.Binance
|
||||
"AB06" -> Blockchain.XRP
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
fun Card.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
|
||||
|
||||
private const val START_2_COIN_ISSUER = "start2coin"
|
||||
private const val TEST_CARD_BATCH = "99FF"
|
||||
|
|
@ -64,21 +48,21 @@ object TapWorkarounds {
|
|||
"TTM BANK"
|
||||
)
|
||||
|
||||
private val notesBatches = listOf(
|
||||
"AB01",
|
||||
"AB02",
|
||||
"AB03",
|
||||
"AB04",
|
||||
"AB05",
|
||||
"AB06",
|
||||
)
|
||||
private val tangemWalletBatches = listOf("AC01")
|
||||
|
||||
private val multiCurrencyWalletsBatches = listOf("AC01")
|
||||
private val tangemNoteBatches = mapOf(
|
||||
"AB01" to Blockchain.Bitcoin,
|
||||
"AB02" to Blockchain.Ethereum,
|
||||
"AB03" to Blockchain.CardanoShelley,
|
||||
"AB04" to Blockchain.Dogecoin,
|
||||
"AB05" to Blockchain.Binance,
|
||||
"AB06" to Blockchain.XRP,
|
||||
)
|
||||
}
|
||||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
get() {
|
||||
return !isTwinCard() && !isStart2Coin && !isNote()
|
||||
return !isTangemTwin() && !isStart2Coin && !isTangemNote()
|
||||
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
|
||||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
|
||||
}
|
||||
38
app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
Normal file
38
app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.tap.domain
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.Target
|
||||
import com.tangem.common.services.Result
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UrlBitmapLoader {
|
||||
|
||||
fun loadBitmap(url: String, callback: (Result<Bitmap>) -> Unit) {
|
||||
Picasso.get().load(url).into(DownloadTarget(callback))
|
||||
}
|
||||
|
||||
fun loadBitmap(url: String, target: DownloadTarget) {
|
||||
Picasso.get().load(url).into(target)
|
||||
}
|
||||
}
|
||||
|
||||
// It adds the ability to trigger multiple downloads with a unique callback.
|
||||
open class DownloadTarget(
|
||||
val callback: (Result<Bitmap>) -> Unit,
|
||||
) : Target {
|
||||
|
||||
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
||||
callback(Result.Success(bitmap))
|
||||
}
|
||||
|
||||
override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) {
|
||||
callback.invoke(Result.Failure(e))
|
||||
}
|
||||
|
||||
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@ package com.tangem.tap.domain.extensions
|
|||
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.features.wallet.redux.Artwork
|
||||
|
||||
fun Card.getSingleWallet(): CardWallet? {
|
||||
return wallets.firstOrNull()
|
||||
|
|
@ -9,6 +12,10 @@ fun Card.getSingleWallet(): CardWallet? {
|
|||
|
||||
fun Card.hasWallets(): Boolean = wallets.isNotEmpty()
|
||||
|
||||
fun Card.hasNoWallets(): Boolean = wallets.isEmpty()
|
||||
|
||||
fun Card.hasSingleWallet(): Boolean = wallets.size == 1
|
||||
|
||||
fun Card.hasSignedHashes(): Boolean {
|
||||
return wallets.any { it.totalSignedHashes ?: 0 > 0 }
|
||||
}
|
||||
|
|
@ -17,6 +24,17 @@ fun Card.signedHashesCount(): Int {
|
|||
return wallets.map { it.totalSignedHashes ?: 0 }.sum()
|
||||
}
|
||||
|
||||
fun Card.getArtworkUrl(artworkId: String?): String? {
|
||||
return when {
|
||||
artworkId != null -> {
|
||||
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
}
|
||||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val Card.remainingSignatures: Int?
|
||||
get() = this.getSingleWallet()?.remainingSignatures
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTestCard
|
||||
import com.tangem.tap.domain.tasks.ScanNoteResponse
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
card: Card,
|
||||
|
|
@ -54,16 +54,18 @@ fun WalletManagerFactory.makePrimaryWalletManager(
|
|||
): WalletManager? {
|
||||
val card = data.card
|
||||
val blockchain = if (card.isTestCard) {
|
||||
data.getBlockchain()?.getTestnetVersion()
|
||||
data.getBlockchain().getTestnetVersion()
|
||||
} else {
|
||||
data.getBlockchain()
|
||||
}
|
||||
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
|
||||
|
||||
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
|
||||
val wallet = selectWallet(wallets)
|
||||
val publicKey = wallet?.publicKey ?: return null
|
||||
val curveToUse = wallet.curve ?: return null
|
||||
return if (card.isTwinCard() && data.secondTwinPublicKey != null) {
|
||||
val curveToUse = wallet.curve
|
||||
|
||||
return if (card.isTangemTwin() && data.secondTwinPublicKey != null) {
|
||||
makeMultisigWalletManager(
|
||||
cardId = card.cardId,
|
||||
walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ 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.Token
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
|
|
@ -17,12 +15,12 @@ import com.tangem.operations.CommandResponse
|
|||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
|
||||
import com.tangem.tap.domain.TapWorkarounds.isExcluded
|
||||
import com.tangem.tap.domain.TapWorkarounds.noteCurrency
|
||||
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
|
||||
data class ScanNoteResponse(
|
||||
val card: Card,
|
||||
|
|
@ -30,9 +28,9 @@ data class ScanNoteResponse(
|
|||
val secondTwinPublicKey: String? = null,
|
||||
) : CommandResponse {
|
||||
|
||||
fun getBlockchain(): Blockchain? {
|
||||
if (card.noteCurrency != null) return card.noteCurrency
|
||||
val blockchainName: String = walletData?.blockchain ?: return null
|
||||
fun getBlockchain(): Blockchain {
|
||||
if (card.isTangemNote()) return card.getTangemNoteBlockchain() ?: return Blockchain.Unknown
|
||||
val blockchainName: String = walletData?.blockchain ?: return Blockchain.Unknown
|
||||
return Blockchain.fromId(blockchainName)
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +65,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
return@run
|
||||
}
|
||||
|
||||
if (card.isTwinCard()) {
|
||||
if (card.isTangemTwin()) {
|
||||
dealWithTwinCard(card, session, callback)
|
||||
} else if (card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) {
|
||||
createMissingWalletsIfNeeded(card, session, callback)
|
||||
|
|
@ -146,20 +144,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable<ScanNoteRespons
|
|||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
|
||||
// Disable new multi-currency HD wallet cards on the old version of the app
|
||||
if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard()
|
||||
// if (card.isMultiCurrencyWallet()) return UpdateAppToUseThisCard()
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getWalletManagerFactory(): WalletManagerFactory {
|
||||
val blockchainSdkConfig = store.state.globalState.configManager?.config
|
||||
?.blockchainSdkConfig ?: BlockchainSdkConfig()
|
||||
return WalletManagerFactory(blockchainSdkConfig)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UpdateAppToUseThisCard : TangemError {
|
||||
override val code: Int = 50005
|
||||
override var customMessage: String = code.toString()
|
||||
override val messageResId: Int = R.string.error_update_app
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ enum class TwinCardNumber(val number: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Card.isTwinCard(): Boolean {
|
||||
fun Card.isTangemTwin(): Boolean {
|
||||
return getTwinCardNumber(cardId) != null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import com.tangem.tap.domain.extensions.isWalletDataSupported
|
|||
import com.tangem.tap.domain.extensions.signedHashesCount
|
||||
import com.tangem.tap.domain.extensions.toSendableAmounts
|
||||
import com.tangem.tap.domain.twins.TwinsHelper
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletReducer
|
||||
import com.tangem.tap.features.details.redux.twins.CreateTwinWalletState
|
||||
import com.tangem.tap.features.wallet.models.toPendingTransactions
|
||||
import com.tangem.tap.features.wallet.models.hasPendingTransactions
|
||||
import org.rekotlin.Action
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
}
|
||||
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: DetailsState): DetailsState {
|
||||
val twinsState = if (action.card.isTwinCard()) {
|
||||
val twinsState = if (action.card.isTangemTwin()) {
|
||||
CreateTwinWalletState(
|
||||
scanResponse = action.scanNoteResponse,
|
||||
twinCardNumber = TwinsHelper.getTwinCardNumber(action.card.cardId),
|
||||
|
|
@ -72,12 +72,10 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: Deta
|
|||
private fun handleEraseWallet(action: DetailsAction.EraseWallet, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
DetailsAction.EraseWallet.Check -> {
|
||||
val notAllowedByCard =
|
||||
state.card?.settings?.isPermanentWallet == true
|
||||
|| state.card?.isWalletDataSupported == true
|
||||
val notAllowedByAnyWallet = state.card?.wallets?.any { it.settings.isPermanent } ?: false
|
||||
val notAllowedByCard = notAllowedByAnyWallet || state.card?.isWalletDataSupported == true
|
||||
val notEmpty = state.wallets.any {
|
||||
!it.recentTransactions.toPendingTransactions(it.address).isNullOrEmpty()
|
||||
|| it.amounts.toSendableAmounts().isNotEmpty()
|
||||
it.hasPendingTransactions() || it.amounts.toSendableAmounts().isNotEmpty()
|
||||
}
|
||||
val eraseWalletState = when {
|
||||
notAllowedByCard -> EraseWalletState.NotAllowedByCard
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ 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.domain.twins.getTwinCardIdForUser
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
|
|
@ -68,7 +68,7 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
|
||||
|
||||
if (state.cardInfo != null) {
|
||||
val cardId = if (state.card?.isTwinCard() == true) {
|
||||
val cardId = if (state.card?.isTangemTwin() == true) {
|
||||
state.card.getTwinCardIdForUser()
|
||||
} else {
|
||||
state.cardInfo.cardId
|
||||
|
|
@ -78,8 +78,8 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber<Det
|
|||
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_signed_hashes.show(state.card?.isTangemTwin() != true)
|
||||
tv_signed_hashes_title.show(state.card?.isTangemTwin() != true)
|
||||
|
||||
tv_disclaimer.setOnClickListener { store.dispatch(DetailsAction.ShowDisclaimer) }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.features.wallet.models
|
|||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.tap.common.extensions.toFormattedString
|
||||
|
||||
data class PendingTransaction(
|
||||
|
|
@ -57,4 +58,12 @@ fun TransactionData.toPendingTransactionForToken(token: Token, walletAddress: St
|
|||
|
||||
fun List<TransactionData>.toPendingTransactionsForToken(token: Token, walletAddress: String): List<PendingTransaction> {
|
||||
return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) }
|
||||
}
|
||||
|
||||
fun Wallet.getPendingTransactions(): List<PendingTransaction> {
|
||||
return recentTransactions.toPendingTransactions(address)
|
||||
}
|
||||
|
||||
fun Wallet.hasPendingTransactions(): Boolean {
|
||||
return recentTransactions.toPendingTransactions(address).isNotEmpty()
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import com.tangem.tap.domain.extensions.getSingleWallet
|
|||
import com.tangem.tap.domain.extensions.hasSignedHashes
|
||||
import com.tangem.tap.domain.extensions.remainingSignatures
|
||||
import com.tangem.tap.domain.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.twins.isTwinCard
|
||||
import com.tangem.tap.domain.twins.isTangemTwin
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.network.NetworkConnectivity
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
|
@ -41,7 +41,7 @@ class WarningsMiddleware {
|
|||
is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline()
|
||||
is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
|
||||
val cardId = globalState?.scanNoteResponse?.card?.cardId
|
||||
cardId?.let { preferencesStorage.saveScannedCardId(it) }
|
||||
cardId?.let { preferencesStorage.usedCardsPrefStorage.scanned(it) }
|
||||
}
|
||||
is WalletAction.Warnings.AppRating.RemindLater -> {
|
||||
preferencesStorage.appRatingLaunchObserver.applyDelayedShowing()
|
||||
|
|
@ -91,7 +91,7 @@ class WarningsMiddleware {
|
|||
showWarningLowRemainingSignaturesIfNeeded(card)
|
||||
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
|
||||
addWarningMessage(WarningMessagesManager.devCardWarning())
|
||||
} else if (!preferencesStorage.wasCardScannedBefore(card.cardId)) {
|
||||
} else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) {
|
||||
checkIfWarningNeeded(card)?.let { warning -> addWarningMessage(warning) }
|
||||
}
|
||||
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release) {
|
||||
|
|
@ -119,7 +119,7 @@ class WarningsMiddleware {
|
|||
private fun checkIfWarningNeeded(
|
||||
card: Card,
|
||||
): WarningMessage? {
|
||||
if (card.isTwinCard()) return null
|
||||
if (card.isTangemTwin()) return null
|
||||
|
||||
if (card.isMultiwalletAllowed) {
|
||||
return if (card.hasSignedHashes()) {
|
||||
|
|
@ -150,9 +150,9 @@ class WarningsMiddleware {
|
|||
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return
|
||||
|
||||
val card = store.state.globalState.scanNoteResponse?.card
|
||||
if (card == null || preferencesStorage.wasCardScannedBefore(card.cardId)) return
|
||||
if (card == null || preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) return
|
||||
|
||||
if (card.isTwinCard() || card.isMultiwalletAllowed) return
|
||||
if (card.isTangemTwin() || card.isMultiwalletAllowed) return
|
||||
|
||||
val validator = store.state.walletState.walletManagers.firstOrNull()
|
||||
as? SignatureCountValidator
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@ package com.tangem.tap.features.wallet.redux.reducers
|
|||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.FiatCurrencyName
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getArtworkUrl
|
||||
import com.tangem.tap.domain.getFirstToken
|
||||
import com.tangem.tap.domain.twins.TwinCardNumber
|
||||
import com.tangem.tap.features.wallet.redux.*
|
||||
|
|
@ -215,29 +214,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState {
|
|||
is WalletAction.LoadFiatRate.Success ->
|
||||
newState = setNewFiatRate(action.fiatRate, state.globalState.appCurrency, newState)
|
||||
is WalletAction.LoadArtwork -> {
|
||||
val cardId = action.card.cardId
|
||||
val cardPublicKey = action.card.cardPublicKey.toHexString()
|
||||
val artworkUrl = when {
|
||||
action.artworkId != null -> {
|
||||
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey, action.artworkId)
|
||||
}
|
||||
action.card.cardId.startsWith(Artwork.SERGIO_CARD_ID) -> {
|
||||
Artwork.SERGIO_CARD_URL
|
||||
}
|
||||
action.card.cardId.startsWith(Artwork.MARTA_CARD_ID) -> {
|
||||
Artwork.MARTA_CARD_URL
|
||||
}
|
||||
newState.twinCardsState?.cardNumber != null -> {
|
||||
when (newState.twinCardsState?.cardNumber) {
|
||||
val artworkUrl = action.card.getArtworkUrl(action.artworkId)
|
||||
?: when (newState.twinCardsState?.cardNumber) {
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2
|
||||
null -> Artwork.DEFAULT_IMG_URL
|
||||
else -> Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Artwork.DEFAULT_IMG_URL
|
||||
}
|
||||
}
|
||||
newState = newState.copy(cardImage = Artwork(artworkId = artworkUrl))
|
||||
}
|
||||
is WalletAction.ShowDialog.QrCode -> {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
private val preferences: SharedPreferences = applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
val appRatingLaunchObserver: AppRatingLaunchObserver
|
||||
val usedCardsPrefStorage: UsedCardsPrefStorage
|
||||
|
||||
init {
|
||||
incrementLaunchCounter()
|
||||
appRatingLaunchObserver = AppRatingLaunchObserver(preferences, getCountOfLaunches())
|
||||
usedCardsPrefStorage = UsedCardsPrefStorage(preferences)
|
||||
usedCardsPrefStorage.migrate()
|
||||
}
|
||||
|
||||
private val fiatCurrenciesAdapter: JsonAdapter<List<FiatCurrency>> by lazy {
|
||||
|
|
@ -54,20 +57,11 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
|
||||
fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
|
||||
|
||||
fun saveScannedCardId(cardId: String) {
|
||||
val scannedCardsIds: String = restoreScannedCardIds()
|
||||
if (!scannedCardsIds.contains(cardId)) {
|
||||
preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use UsedCardsPrefStorage instead")
|
||||
fun wasCardScannedBefore(cardId: String): Boolean {
|
||||
return restoreScannedCardIds().contains(cardId)
|
||||
return usedCardsPrefStorage.wasScanned(cardId)
|
||||
}
|
||||
|
||||
private fun restoreScannedCardIds(): String =
|
||||
preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: ""
|
||||
|
||||
fun saveDisclaimerAccepted() {
|
||||
preferences.edit().putBoolean(DISCLAIMER_ACCEPTED_KEY, true).apply()
|
||||
}
|
||||
|
|
@ -93,7 +87,6 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
private const val PREFERENCES_NAME = "tapPrefs"
|
||||
private const val APP_CURRENCY_KEY = "appCurrency"
|
||||
private const val FIAT_CURRENCIES_KEY = "fiatCurrencies"
|
||||
private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds"
|
||||
private const val DISCLAIMER_ACCEPTED_KEY = "disclaimerAccepted"
|
||||
private const val TWINS_ONBOARDING_SHOWN_KEY = "twinsOnboardingShown"
|
||||
private const val APP_LAUNCH_COUNT_KEY = "launchCount"
|
||||
|
|
@ -102,8 +95,8 @@ class PreferencesStorage(applicationContext: Application) {
|
|||
}
|
||||
|
||||
class AppRatingLaunchObserver(
|
||||
private val preferences: SharedPreferences,
|
||||
private val launchCounts: Int,
|
||||
private val preferences: SharedPreferences,
|
||||
private val launchCounts: Int,
|
||||
) {
|
||||
private val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount"
|
||||
private val K_FUNDS_FOUND_DATE = "fundsFoundDate"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.tap.persistence
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.tap.common.extensions.replaceBy
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UsedCardsPrefStorage(
|
||||
private val preferences: SharedPreferences,
|
||||
) {
|
||||
|
||||
private val jsonConverter = MoshiJsonConverter.INSTANCE
|
||||
|
||||
internal fun migrate() {
|
||||
val scannedIds = preferences.getString(SCANNED_CARDS_IDS_KEY, null) ?: return
|
||||
|
||||
val usedCardsInfo = scannedIds.split(",").map { UsedCardInfo(it.trim(), true) }
|
||||
if (save(usedCardsInfo.toMutableList())) {
|
||||
preferences.edit { this.remove(SCANNED_CARDS_IDS_KEY) }
|
||||
}
|
||||
}
|
||||
|
||||
fun scanned(cardId: String) {
|
||||
val restoredList = restore()
|
||||
val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true)
|
||||
?: UsedCardInfo(cardId, true)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun wasScanned(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isScanned ?: false
|
||||
}
|
||||
|
||||
fun activationStarted(cardId: String) {
|
||||
val restoredList = restore()
|
||||
val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true)
|
||||
?: UsedCardInfo(cardId, isActivationStarted = true)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun activationIsStarted(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivationStarted ?: false
|
||||
}
|
||||
|
||||
fun activated(cardId: String) {
|
||||
val restoredList = restore()
|
||||
val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivated = true)
|
||||
?: UsedCardInfo(cardId, isActivated = true)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun wasActivated(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivated ?: false
|
||||
}
|
||||
|
||||
private fun findCardInfo(cardId: String, list: MutableList<UsedCardInfo>? = null): UsedCardInfo? {
|
||||
val findInList = list ?: restore()
|
||||
return findInList.firstOrNull { it.cardId == cardId }
|
||||
}
|
||||
|
||||
private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList<UsedCardInfo>) {
|
||||
val info = usedCardInfo ?: return
|
||||
|
||||
usedCardsInfo.replaceBy(info) { it.cardId == info.cardId }
|
||||
save(usedCardsInfo)
|
||||
}
|
||||
|
||||
private fun save(list: MutableList<UsedCardInfo>): Boolean {
|
||||
return try {
|
||||
val json = jsonConverter.toJson(list)
|
||||
preferences.edit { putString(USED_CARDS_INFO, json) }
|
||||
true
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun restore(): MutableList<UsedCardInfo> {
|
||||
val json = preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf()
|
||||
return try {
|
||||
jsonConverter.fromJson(json)!!
|
||||
} catch (ex: Exception) {
|
||||
preferences.edit(true) { remove(USED_CARDS_INFO) }
|
||||
mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val USED_CARDS_INFO = "usedCardsInfo"
|
||||
private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds"
|
||||
}
|
||||
}
|
||||
|
||||
private data class UsedCardInfo(
|
||||
val cardId: String,
|
||||
val isScanned: Boolean = false,
|
||||
val isActivationStarted: Boolean = false,
|
||||
val isActivated: Boolean = false,
|
||||
)
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
<resources>
|
||||
<string name="scan_card_error" translatable="false">Scan card failed. Try again</string>
|
||||
|
||||
<string name="wallet_button_buy" translatable="false">Buy</string>
|
||||
<string name="wallet_button_sell" translatable="false">Sell</string>
|
||||
<string name="wallet_button_trade" translatable="false">Trade</string>
|
||||
|
|
@ -168,4 +170,28 @@ this wallet.
|
|||
<string name="wallet_dialog_choose_trade_action" translatable="false">Do you want to buy or sell crypto?</string>
|
||||
|
||||
<string name="error_update_app" translatable="false">Oops, the current version of the application is not ready to work with this card, please check for updates.</string>
|
||||
|
||||
<!-- Onboarding -->
|
||||
<string name="home_welcome_header" translatable="false">Welcome to Tangem</string>
|
||||
<string name="home_welcome_body" translatable="false">The safest way to buy, use and\n store cryptocurrency</string>
|
||||
<string name="home_button_get_new_card" translatable="false">Get a new card</string>
|
||||
|
||||
<string name="onboarding_title" translatable="false">Activating card</string>
|
||||
<string name="onboarding_button_what_does_it_mean" translatable="false">What does it mean?</string>
|
||||
<string name="onboarding_create_wallet_header" translatable="false">Create a wallet</string>
|
||||
<string name="onboarding_create_wallet_body" translatable="false">Let’s generate all the keys on your card and create a secure wallet</string>
|
||||
<string name="onboarding_create_wallet_button_create_wallet" translatable="false">Create wallet</string>
|
||||
|
||||
<string name="onboarding_top_up_header" translatable="false">Top up your wallet</string>
|
||||
<string name="onboarding_top_up_body" translatable="false">To get started, simply top up the card with any amount</string>
|
||||
<string name="onboarding_top_up_button_but_crypto" translatable="false">Buy crypto</string>
|
||||
<string name="onboarding_top_up_button_show_wallet_address" translatable="false">Show the wallet’s address</string>
|
||||
<string name="onboarding_dialog_wallet_address" translatable="false">Scan the address to top up your wallet</string>
|
||||
|
||||
<string name="onboarding_done_header" translatable="false">Done!</string>
|
||||
<string name="onboarding_done_body" translatable="false">Your crypto card is activated and ready to be used</string>
|
||||
<string name="onboarding_done_button_continue" translatable="false">Continue</string>
|
||||
|
||||
<string name="onboarding_error_create_primary_wallet" translatable="false">Internal error: can\'t create wallet manager</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue