From 5eb8b71a1f3526c1b36836688d590e1a177d46a6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 23 Sep 2021 11:28:43 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 4 +- .../tap/common/extensions/Collections.kt | 7 ++ .../common/redux/NotificationsMiddleware.kt | 45 +++++--- .../com/tangem/tap/domain/TangemSdkManager.kt | 22 ++-- .../tangem/tap/domain/TapBlockchainConfig.kt | 13 --- .../java/com/tangem/tap/domain/TapErrors.kt | 5 +- .../com/tangem/tap/domain/TapWalletManager.kt | 10 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 46 +++----- .../com/tangem/tap/domain/UrlBitmapLoader.kt | 38 +++++++ .../com/tangem/tap/domain/extensions/Card.kt | 18 +++ .../domain/extensions/WalletManagerFactory.kt | 10 +- .../tangem/tap/domain/tasks/ScanNoteTask.kt | 33 ++---- .../tangem/tap/domain/twins/TwinsHelper.kt | 2 +- .../features/details/redux/DetailsReducer.kt | 14 +-- .../features/details/ui/DetailsFragment.kt | 8 +- .../wallet/models/PendingTransaction.kt | 9 ++ .../redux/middlewares/WarningsMiddleware.kt | 12 +- .../wallet/redux/reducers/WalletReducer.kt | 26 +---- .../tap/persistence/PreferencesStorage.kt | 21 ++-- .../tap/persistence/UsedCardsPrefStorage.kt | 107 ++++++++++++++++++ .../main/res/values/strings_untranslated.xml | 26 +++++ 21 files changed, 316 insertions(+), 160 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt create mode 100644 app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt diff --git a/app/build.gradle b/app/build.gradle index a1a8cda618..923c2c5326 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt index d1c417e842..b264485c44 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt @@ -14,4 +14,11 @@ fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean { val toRemove = this.filter(predicate) this.removeAll(toRemove) return toRemove.isNotEmpty() +} + +fun MutableList.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) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt index 8a55bcfafe..c797764bbd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt @@ -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?): String { val notificationsMiddleware: Middleware = { 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 -} \ No newline at end of file +} + +// Processed only in the debug builds +interface Debug +interface DebugNotification : Debug, NotificationAction +interface DebugToastNotification : Debug, ToastNotificationAction +interface DebugErrorAction : Debug, ErrorAction \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 56fe47fb45..adc0404f24 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -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 { 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 + ) { + 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 { @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt b/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt deleted file mode 100644 index b9ec4705a5..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/TapBlockchainConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.tap.domain - -class TapBlockchainConfig { - companion object { - val blockchairApiKey: String = "A___0Shpsu4KagE7oSabrw20DfXAqWlT" - val blockcypherTokens: Set = setOf( - "aa8184b0e0894b88a5688e01b3dc1e82", - "56c4ca23c6484c8f8864c32fde4def8d", - "66a8a37c5e9d4d2c9bb191acfe7f93aa" - ) - val infuraProjectId: String = "613a0b14833145968b1f656240c7d245" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt index ca57897238..8ad394c14f 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -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) : 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( diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 466e463f19..0193a9d2a1 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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 -> { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt index 8af0e99415..ffc138b170 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt new file mode 100644 index 0000000000..d09d8ed3bc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/UrlBitmapLoader.kt @@ -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) -> 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) -> 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?) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index aeef948edf..dc3fc1a618 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index 5ec3f1afa9..0ab0ec0293 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -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(), diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 9f31169566..510d2785d8 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -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= FirmwareVersion.MultiWalletAvailable) { createMissingWalletsIfNeeded(card, session, callback) @@ -146,20 +144,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { - 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 diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 55f9711482..640cc47cf0 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -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.toPendingTransactionsForToken(token: Token, walletAddress: String): List { return this.mapNotNull { it.toPendingTransactionForToken(token, walletAddress) } +} + +fun Wallet.getPendingTransactions(): List { + return recentTransactions.toPendingTransactions(address) +} + +fun Wallet.hasPendingTransactions(): Boolean { + return recentTransactions.toPendingTransactions(address).isNotEmpty() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index d3a6c2d046..d69b213ece 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 9bdef4b340..8c6db9d9a2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -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 -> { diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt index 871e3eb3d5..77c8be322d 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -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> 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" diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt new file mode 100644 index 0000000000..b6ebfd8a32 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt @@ -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? = null): UsedCardInfo? { + val findInList = list ?: restore() + return findInList.firstOrNull { it.cardId == cardId } + } + + private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList) { + val info = usedCardInfo ?: return + + usedCardsInfo.replaceBy(info) { it.cardId == info.cardId } + save(usedCardsInfo) + } + + private fun save(list: MutableList): 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 { + 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, +) \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 9404210575..888918f16b 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -1,4 +1,6 @@ + Scan card failed. Try again + Buy Sell Trade @@ -168,4 +170,28 @@ this wallet. Do you want to buy or sell crypto? Oops, the current version of the application is not ready to work with this card, please check for updates. + + + Welcome to Tangem + The safest way to buy, use and\n store cryptocurrency + Get a new card + + Activating card + What does it mean? + Create a wallet + Let’s generate all the keys on your card and create a secure wallet + Create wallet + + Top up your wallet + To get started, simply top up the card with any amount + Buy crypto + Show the wallet’s address + Scan the address to top up your wallet + + Done! + Your crypto card is activated and ready to be used + Continue + + Internal error: can\'t create wallet manager +