From 7e6079884920cb2598d6fb0dded824f063ab49fb Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Sep 2020 22:21:44 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 2 + .../com/tangem/tap/domain/TangemSdkManager.kt | 4 +- .../java/com/tangem/tap/domain/TapErrors.kt | 10 ++- .../com/tangem/tap/domain/TapWalletManager.kt | 2 + .../tangem/tap/domain/tasks/ScanNoteTask.kt | 27 +++++++ .../features/details/redux/DetailsReducer.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 14 +++- .../features/wallet/redux/WalletMiddleware.kt | 80 ++++++++++++++++++- .../features/wallet/redux/WalletReducer.kt | 21 +++-- .../tap/features/wallet/redux/WalletState.kt | 5 ++ .../tap/features/wallet/ui/WalletFragment.kt | 6 ++ .../features/wallet/ui/dialogs/QrDialog.kt | 4 +- .../wallet/ui/dialogs/WarningDialog.kt | 30 +++++++ .../tap/persistence/PreferencesStorage.kt | 16 +++- app/src/main/res/values/strings.xml | 8 +- 15 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt diff --git a/app/build.gradle b/app/build.gradle index 021f3fe282..bdfb6fba80 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,6 +65,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10' implementation 'com.tangem:blockchain:1.49.0' + implementation 'com.tangem:core:1.57.0' + implementation 'com.tangem:sdk:1.57.0' //lifecycle implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" 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 38dcfe6358..c4dc1b2f7b 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -34,11 +34,11 @@ class TangemSdkManager(val activity: ComponentActivity) { } suspend fun setPasscode(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId) + return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId) } suspend fun setAccessCode(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain(SetPinCommand.setPin2(null), cardId) + return runTaskAsyncReturnOnMain(SetPinCommand.setPin1(null), cardId) } suspend fun setLongTap(cardId: String?): CompletionResult { 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 b6668d0754..b1a8bbdbeb 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapErrors.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapErrors.kt @@ -1,8 +1,8 @@ package com.tangem.tap.domain import androidx.annotation.StringRes +import com.tangem.TangemError import com.tangem.wallet.R -import java.math.BigDecimal interface TapErrors interface MultiMessageError : TapErrors { @@ -34,6 +34,14 @@ sealed class TapError(@StringRes val localizedMessage: Int) : Throwable(), TapEr ) : TapError(-1), MultiMessageError } +sealed class TapSdkError(override val messageResId: Int?) : Throwable(), TangemError { + final override val code: Int = 1 + override var customMessage: String = code.toString() + + object CardForDifferentApp : TapSdkError(R.string.error_card_for_different_app) +} + + fun TapErrors.assembleErrorIds(): MutableList { val idList = mutableListOf() when (this) { 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 02e5662a9b..90e19278fd 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -79,7 +79,9 @@ class TapWalletManager { suspend fun onCardScanned(data: ScanNoteResponse) { withContext(Dispatchers.Main) { + store.dispatch(WalletAction.ResetState) store.dispatch(GlobalAction.SaveScanNoteResponse(data)) + store.dispatch(WalletAction.CheckIfWarningNeeded) val artworkId = data.verifyResponse?.artworkInfo?.id if (data.walletManager != null) { if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { 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 d773a34f8a..7f5e2810db 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,14 +2,18 @@ package com.tangem.tap.domain.tasks import com.tangem.CardSession import com.tangem.CardSessionRunnable +import com.tangem.TangemError import com.tangem.TangemSdkError import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.commands.Card +import com.tangem.commands.CardStatus import com.tangem.commands.CommandResponse +import com.tangem.commands.Product import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult +import com.tangem.tap.domain.TapSdkError import com.tangem.tasks.ScanTask data class ScanNoteResponse( @@ -29,6 +33,12 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable { val card = this.card ?: result.data + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return@run + } + val walletManager = try { WalletManagerFactory.makeWalletManager(card) } catch (exception: Exception) { @@ -50,4 +60,21 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable?) : WalletAction() object Cancel : WalletAction() } + object CreatePayId : WalletAction() { data class CompleteCreatingPayId(val payId: String) : WalletAction() data class Success(val payId: String) : WalletAction() @@ -68,7 +78,7 @@ sealed class WalletAction : Action { } object ShowQrCode : WalletAction() - object HideQrCode : WalletAction() + object HideDialog : WalletAction() data class ExploreAddress(val context: Context) : WalletAction() object CreateWallet : WalletAction() object EmptyWallet : WalletAction() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt index aa5556d05f..8f279e2540 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletMiddleware.kt @@ -5,7 +5,10 @@ import android.net.Uri import androidx.core.content.ContextCompat import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.SignatureCountValidator import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.commands.Card import com.tangem.commands.common.network.Result import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString @@ -17,7 +20,9 @@ import com.tangem.tap.domain.PayIdManager import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.toSendableAmounts import com.tangem.tap.features.send.redux.PrepareSendScreen +import com.tangem.tap.network.NetworkConnectivity import com.tangem.tap.network.NetworkStateChanged +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -65,7 +70,10 @@ val walletMiddleware: Middleware = { dispatch, state -> scope.launch { store.state.globalState.tapWalletManager.updateWallet() } } is WalletAction.UpdateWallet.Success -> setupWalletUpdate(action.wallet) - is WalletAction.LoadWallet.Success -> setupWalletUpdate(action.wallet) + is WalletAction.LoadWallet.Success -> { + store.dispatch(WalletAction.CheckHashesCountOnline) + setupWalletUpdate(action.wallet) + } is WalletAction.CreatePayId.CompleteCreatingPayId -> { scope.launch { val cardId = store.state.globalState.scanNoteResponse?.card?.cardId @@ -100,13 +108,21 @@ val walletMiddleware: Middleware = { dispatch, state -> } } } - is WalletAction.LoadData, is NetworkStateChanged -> { + is WalletAction.LoadData -> { scope.launch { store.state.globalState.scanNoteResponse?.let { store.state.globalState.tapWalletManager.onCardScanned(it) } } } + is NetworkStateChanged -> { + store.state.globalState.scanNoteResponse?.let { scanNoteResponse -> + store.dispatch(WalletAction.CheckHashesCountOnline) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(scanNoteResponse) + } + } + } is WalletAction.CopyAddress -> { store.state.walletState.addressData?.address?.let { action.context.copyToClipboard(it) @@ -125,6 +141,20 @@ val walletMiddleware: Middleware = { dispatch, state -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Send)) } } + is WalletAction.CheckIfWarningNeeded -> { + val card = store.state.globalState.scanNoteResponse?.card + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + if (card != null && !preferencesStorage.wasCardScannedBefore(card.cardId)) { + val result = checkIfWarningNeeded(card, validator) + if (result != null) store.dispatch(WalletAction.ShowWarning(result)) + } + } + is WalletAction.CheckHashesCountOnline -> checkHashesCountOnline() + is WalletAction.SaveCardId -> { + val cardId = store.state.globalState.scanNoteResponse?.card?.cardId + cardId?.let { preferencesStorage.saveScannedCardId(it) } + } } next(action) } @@ -132,7 +162,7 @@ val walletMiddleware: Middleware = { dispatch, state -> } private fun setupWalletUpdate(wallet: Wallet) { - if (!wallet.recentTransactions.isNullOrEmpty()) { + if (!wallet.recentTransactions.isNullOrEmpty() && !store.state.walletState.updatingWallet) { scope.launch(Dispatchers.IO) { delay(10000) withContext(Dispatchers.Main) { @@ -159,4 +189,48 @@ private fun prepareSendAction(amount: Amount?): Action { PrepareSendScreen(amountToSend) } } +} + +private fun checkIfWarningNeeded( + card: Card, signatureCountValidator: SignatureCountValidator? = null +): WarningType? { + +// if (card.getType() != CardType.Release) { +// return WarningType.DevCard +// } + + return if (signatureCountValidator == null) { + if (card.walletSignedHashes ?: 0 > 0) { + WarningType.CardSignedHashesBefore + } else { + store.dispatch(WalletAction.SaveCardId) + null + } + } else { + store.dispatch(WalletAction.NeedToCheckHashesCountOnline) + null + } +} + +private fun checkHashesCountOnline() { + if (store.state.walletState.hashesCountVerified != false) return + + if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) return + + val card = store.state.globalState.scanNoteResponse?.card + val validator = store.state.globalState.scanNoteResponse?.walletManager + as? SignatureCountValidator + + scope.launch { + val result = validator?.validateSignatureCount(card?.walletSignedHashes + ?: 0) + withContext(Dispatchers.Main) { + when (result) { + SimpleResult.Success -> store.dispatch(WalletAction.ConfirmHashesCount) + is SimpleResult.Failure -> store.dispatch( + WalletAction.ShowWarning(WarningType.CardSignedHashesBefore) + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt index 2d69c10626..c751d15e4f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletReducer.kt @@ -31,7 +31,8 @@ private fun internalReduce(action: Action, state: AppState): WalletState { var newState = state.walletState when (action) { - is WalletAction.EmptyWallet -> newState = WalletState( + is WalletAction.ResetState -> newState = WalletState() + is WalletAction.EmptyWallet -> newState = newState.copy( state = ProgressState.Done, currencyData = BalanceWidgetData(BalanceStatus.EmptyCard), mainButton = WalletMainButton.CreateWalletButton(true) @@ -45,7 +46,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } else { AddressData(wallet.address, wallet.shareUrl, wallet.exploreUrl) } - newState = WalletState( + newState = newState.copy( state = ProgressState.Error, error = ErrorType.NoInternetConnection, addressData = addressData, @@ -56,7 +57,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } is TapError.UnknownBlockchain -> { - newState = WalletState( + newState = newState.copy( state = ProgressState.Done, currencyData = BalanceWidgetData(BalanceStatus.UnknownBlockchain) ) @@ -77,7 +78,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { } else { null } - newState = WalletState( + newState = newState.copy( state = ProgressState.Loading, cardImage = cardImage, currencyData = BalanceWidgetData( @@ -109,7 +110,9 @@ private fun internalReduce(action: Action, state: AppState): WalletState { errorMessage = action.errorMessage ) ) + is WalletAction.UpdateWallet -> newState = newState.copy(updatingWallet = true) is WalletAction.UpdateWallet.Success -> newState = onWalletLoaded(action.wallet, newState) + is WalletAction.UpdateWallet.Failure -> newState = newState.copy(updatingWallet = false) is WalletAction.LoadFiatRate -> { newState.copy(currencyData = newState.currencyData.copy( fiatAmount = null, @@ -158,7 +161,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) ) } - is WalletAction.HideQrCode -> { + is WalletAction.HideDialog -> { newState = newState.copy(walletDialog = null) } is WalletAction.LoadPayId.Success -> newState = newState.copy( @@ -185,7 +188,12 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) } is WalletAction.Send.Cancel -> newState = newState.copy(walletDialog = null) - + is WalletAction.ShowWarning -> + newState = newState.copy(walletDialog = WalletDialog.WarningDialog(action.warningType)) + is WalletAction.NeedToCheckHashesCountOnline -> + newState = newState.copy(hashesCountVerified = false) + is WalletAction.ConfirmHashesCount -> + newState = newState.copy(hashesCountVerified = true) } return newState } @@ -223,6 +231,7 @@ private fun onWalletLoaded(wallet: Wallet, walletState: WalletState): WalletStat fiatAmount = fiatAmount ), pendingTransactions = pendingTransactions, + updatingWallet = false, mainButton = WalletMainButton.SendButton(sendButtonEnabled) ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 7288a0b082..de06fe71a2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -15,10 +15,12 @@ data class WalletState( val cardImage: Artwork? = null, val wallet: Wallet? = null, val pendingTransactions: List = emptyList(), + val hashesCountVerified: Boolean? = null, val addressData: AddressData? = null, val currencyData: BalanceWidgetData = BalanceWidgetData(), val payIdData: PayIdData = PayIdData(), val walletDialog: WalletDialog? = null, + val updatingWallet: Boolean = false, val mainButton: WalletMainButton = WalletMainButton.SendButton(false) ) : StateType { val showDetails: Boolean = @@ -33,8 +35,11 @@ sealed class WalletDialog { data class CreatePayIdDialog(val creatingPayIdState: CreatingPayIdState?) : WalletDialog() data class SelectAmountToSendDialog(val amounts: List?) : WalletDialog() + data class WarningDialog(val type: WarningType) : WalletDialog() } +enum class WarningType { CardSignedHashesBefore, DevCard } + enum class ProgressState { Loading, Done, Error } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt index d6d8e10c55..a2796e21a7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletFragment.kt @@ -23,6 +23,7 @@ import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendDialog import com.tangem.tap.features.wallet.ui.dialogs.PayIdDialog import com.tangem.tap.features.wallet.ui.dialogs.QrDialog +import com.tangem.tap.features.wallet.ui.dialogs.WarningDialog import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.card_balance.* @@ -229,6 +230,11 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber { + if (dialog == null) dialog = WarningDialog(requireContext()).apply { + this.show(walletDialog.type) + } + } null -> { dialog?.dismiss() dialog = null diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt index 8a201aa749..c3cca80cd1 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/QrDialog.kt @@ -18,8 +18,8 @@ class QrDialog(context: Context) : Dialog(context) { } fun showQr(qrCode: Bitmap, shareUrl: String, currencyName: CryptoCurrencyName?) { - this.setOnDismissListener { store.dispatch(WalletAction.HideQrCode) } - this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideQrCode) } + this.setOnDismissListener { store.dispatch(WalletAction.HideDialog) } + this.btn_done?.setOnClickListener { store.dispatch(WalletAction.HideDialog) } this.tv_qr_dialog_address?.text = shareUrl this.iv_qrcode?.setImageBitmap(qrCode) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt new file mode 100644 index 0000000000..349ba8c45d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/WarningDialog.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.features.wallet.redux.WarningType +import com.tangem.tap.store +import com.tangem.wallet.R + +class WarningDialog(context: Context) : AlertDialog(context) { + + private val dialog: AlertDialog = Builder(context) + .setTitle(context.getString(R.string.generic_warning)) + .setPositiveButton(context.getString(R.string.general_ok)) { _, _ -> + dismiss() + }.setOnDismissListener { + store.dispatch(WalletAction.SaveCardId) + store.dispatch(WalletAction.HideDialog) + } + .create() + + fun show(warningType: WarningType) { + val messageRes = when (warningType) { + WarningType.CardSignedHashesBefore -> R.string.warning_card_signed_transactions + WarningType.DevCard -> R.string.wallet_warning_dev_card + } + dialog.setMessage(dialog.context.getString(messageRes)) + dialog.show() + } +} \ No newline at end of file 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 9ea341307f..fac5d861fc 100644 --- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt +++ b/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt @@ -48,15 +48,29 @@ class PreferencesStorage(applicationContext: Application) { fun isFirstLaunch(): Boolean { val isFirst = !preferences.contains(FIRST_LAUNCH_CHECK_KEY) if (isFirst) preferences.edit().putInt(FIRST_LAUNCH_CHECK_KEY, System.currentTimeMillis().toInt()).apply() - return isFirst } + fun saveScannedCardId(cardId: String) { + val scannedCardsIds: String = restoreScannedCardIds() + if (!scannedCardsIds.contains(cardId)) { + preferences.edit().putString(SCANNED_CARDS_IDS_KEY, "$scannedCardsIds$cardId, ").apply() + } + } + + fun wasCardScannedBefore(cardId: String): Boolean { + return restoreScannedCardIds().contains(cardId) + } + + private fun restoreScannedCardIds(): String = + preferences.getString(SCANNED_CARDS_IDS_KEY, "") ?: "" + companion object { private const val PREFERENCES_NAME = "tapPrefs" private const val APP_CURRENCY_KEY = "appCurrency" private const val FIAT_CURRENCIES_KEY = "fiatCurrencies" private const val FIRST_LAUNCH_CHECK_KEY = "firstLaunchCheck" + private const val SCANNED_CARDS_IDS_KEY = "scannedCardIds" } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 836cd2d341..643e1f0b9c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Cancel Retry and + Warning Address was successfully copied No internet connection @@ -13,7 +14,7 @@ Welcome to Tangem.\nDo you have one of our cards? Welcome back to Tangem Tap. \nScan your card to start. - Yes! + Yes. Scan it! Scan card Shop No @@ -47,10 +48,15 @@ Your PayID is information unique to you, like your phone number, email or ABN. %s wallet + The card you scanned is a development card. Don\'t accept it as a payment. + Warning: This card has been already topped up and signed transactions in the past. + Consider immediate withdrawal of all funds if you have received this card from an untrusted source. This PayID already exists. Try a different one. Error response while creating PayID. + This card it is not designed to work with Tangem Tap + Unknown error PayID verification failed PayID unsupported by blockchain