Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-03 17:39:50 +05:00
commit a3742e91f9
22 changed files with 99 additions and 73 deletions

@ -1 +1 @@
Subproject commit cf6ea50867477f97655da9da47ff94987e8f3354
Subproject commit a1658496e777b611fc990ef2bc1a1a1fd48bc1e6

View file

@ -165,6 +165,7 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
}
is GlobalAction.ScanCard -> {
scope.launch {
tangemSdkManager.changeDisplayedCardIdNumbersCount(null)
val result = tangemSdkManager.scanProduct(
userTokensRepository,
action.additionalBlockchainsToDerive,

View file

@ -180,8 +180,9 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
return withContext(Dispatchers.Main) { result }
}
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse) {
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?) {
tangemSdk.config.cardIdDisplayFormat = when {
scanResponse == null -> CardIdDisplayFormat.Full
scanResponse.isTangemTwins() -> CardIdDisplayFormat.LastLuhn(4)
scanResponse.isSaltPay() -> CardIdDisplayFormat.None
else -> CardIdDisplayFormat.Full

View file

@ -4,12 +4,14 @@ import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemNote
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.extensions.calculateHmacSha256
import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.common.isTangemTwin
import com.tangem.operations.attestation.CardVerifyAndGetInfo
@ -88,4 +90,19 @@ fun Card.getArtworkUrl(artworkId: String?): String? {
cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
else -> null
}
}
fun Card.getUserWalletId(): String {
val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
return UserWalletId(walletPublicKey).stringValue
}
class UserWalletId(val walletPublicKey: ByteArray) {
val stringValue: String = calculateUserId(walletPublicKey)
private fun calculateUserId(walletPublicKey: ByteArray): String {
val message = "UserWalletID".toByteArray()
val keyHash = walletPublicKey.calculateSha256()
return message.calculateHmacSha256(keyHash).toHexString()
}
}

View file

@ -3,14 +3,12 @@ package com.tangem.tap.domain.tokens
import android.content.Context
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.card.Card
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.extensions.calculateHmacSha256
import com.tangem.network.api.tangemTech.TangemTechService
import com.tangem.network.api.tangemTech.UserTokensResponse
import com.tangem.tap.common.AndroidFileReader
import com.tangem.tap.domain.NoDataError
import com.tangem.tap.domain.extensions.getUserWalletId
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.wallet.models.Currency
@ -26,7 +24,7 @@ class UserTokensRepository(
private val networkService: UserTokensNetworkService,
) {
suspend fun getUserTokens(card: Card): List<Currency> {
val userId = card.getUserId()
val userId = card.getUserWalletId()
if (DemoHelper.isDemoCardId(card.cardId)) {
return loadTokensOffline(card, userId).ifEmpty { loadDemoCurrencies() }
}
@ -38,7 +36,7 @@ class UserTokensRepository(
return when (val networkResult = networkService.getUserTokens(userId)) {
is Result.Success -> {
val tokens = networkResult.data.tokens.mapNotNull { Currency.fromTokenResponse(it) }
storageService.saveUserTokens(card.getUserId(), tokens.toUserTokensResponse())
storageService.saveUserTokens(card.getUserWalletId(), tokens.toUserTokensResponse())
tokens.distinct()
}
is Result.Failure -> {
@ -49,14 +47,14 @@ class UserTokensRepository(
suspend fun saveUserTokens(card: Card, tokens: List<Currency>) {
val userTokens = tokens.toUserTokensResponse()
networkService.saveUserTokens(card.getUserId(), userTokens)
storageService.saveUserTokens(card.getUserId(), userTokens)
networkService.saveUserTokens(card.getUserWalletId(), userTokens)
storageService.saveUserTokens(card.getUserWalletId(), userTokens)
}
suspend fun removeUserTokens(card: Card) {
val userTokens = emptyList<Currency>().toUserTokensResponse()
networkService.saveUserTokens(card.getUserId(), userTokens)
storageService.saveUserTokens(card.getUserId(), userTokens)
networkService.saveUserTokens(card.getUserWalletId(), userTokens)
storageService.saveUserTokens(card.getUserWalletId(), userTokens)
}
private fun List<Currency>.toUserTokensResponse(): UserTokensResponse {
@ -69,7 +67,7 @@ class UserTokensRepository(
}
suspend fun loadBlockchainsToDerive(card: Card): List<BlockchainNetwork> {
val userId = card.getUserId()
val userId = card.getUserWalletId()
val blockchainNetworks = loadTokensOffline(card, userId).toBlockchainNetworks()
if (DemoHelper.isDemoCardId(card.cardId)) {
@ -113,11 +111,6 @@ class UserTokensRepository(
return storageService.getUserTokens(userId) ?: storageService.getUserTokens(card)
}
private fun Card.getUserId(): String {
val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
return UserWalletId(walletPublicKey).stringValue
}
companion object {
const val SORT_DEFAULT_VALUE = "manual"
const val GROUP_DEFAULT_VALUE = "none"
@ -131,16 +124,4 @@ class UserTokensRepository(
return UserTokensRepository(storageService, networkService)
}
}
}
data class UserWalletId(
val walletPublicKey: ByteArray,
) {
val stringValue: String = calculateUserId(walletPublicKey)
private fun calculateUserId(walletPublicKey: ByteArray): String {
val message = "UserWalletID".toByteArray()
val keyHash = walletPublicKey.calculateSha256()
return message.calculateHmacSha256(keyHash).toHexString()
}
}

View file

@ -8,12 +8,15 @@ import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.AnalyticsParamAnOld
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
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.extensions.getUserWalletId
import com.tangem.tap.features.demo.DemoHelper
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@ -21,6 +24,7 @@ import com.tangem.tap.features.wallet.models.hasSendableAmountsOrPendingTransact
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -81,7 +85,18 @@ class DetailsMiddleware {
when (val result = tangemSdkManager.scanCard()) {
is CompletionResult.Success -> {
val card = result.data
store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card))
if (card.getUserWalletId() ==
store.state.globalState.scanResponse?.card?.getUserWalletId()
) {
store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card))
} else {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.error_wrong_wallet_tapped,
),
)
}
}
is CompletionResult.Failure -> {
}
@ -107,20 +122,20 @@ class DetailsMiddleware {
val card = store.state.detailsState.cardSettingsState?.card ?: return
scope.launch {
val result = tangemSdkManager.resetToFactorySettings(card)
when (result) {
is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.FactoryResetFinished())
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
Analytics.send(
error,
AnalyticsAnOld.ActionToLog.PurgeWallet,
card = store.state.detailsState.scanResponse?.card,
)
}
when (result) {
is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.FactoryResetFinished())
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
Analytics.send(
error,
AnalyticsAnOld.ActionToLog.PurgeWallet,
card = store.state.detailsState.scanResponse?.card,
)
}
}
}
}
}
@ -181,7 +196,7 @@ class DetailsMiddleware {
is DetailsAction.ManageSecurity.ChangeAccessCode -> {
val card = store.state.detailsState.cardSettingsState?.card ?: return
scope.launch {
when(tangemSdkManager.setAccessCode(card.cardId)){
when (tangemSdkManager.setAccessCode(card.cardId)) {
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
is CompletionResult.Failure -> {}
}

View file

@ -18,6 +18,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
import org.rekotlin.StoreSubscriber
@ -40,6 +41,8 @@ class HomeFragment : Fragment(), StoreSubscriber<HomeState> {
): View? {
val context = container?.context ?: return null
store.dispatch(BackupAction.CheckForUnfinishedBackup)
composeView = ComposeView(context).apply {
setContent {
AppCompatTheme {

View file

@ -37,7 +37,6 @@ import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayExceptionHandler
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
@ -75,7 +74,6 @@ private fun handleHomeAction(appState: () -> AppState?, action: Action, dispatch
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.ExchangeManager.Init)
store.dispatch(GlobalAction.FetchUserCountry)
store.dispatch(BackupAction.CheckForUnfinishedBackup)
}
is HomeAction.ShouldScanCardOnResume -> {
if (action.shouldScanCard) {

View file

@ -20,8 +20,8 @@ import com.tangem.network.api.paymentology.RegistrationResponse
import com.tangem.network.api.paymentology.tryExtractError
import com.tangem.operations.attestation.AttestWalletKeyResponse
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.domain.extensions.UserWalletId
import com.tangem.tap.domain.getFirstToken
import com.tangem.tap.domain.tokens.UserWalletId
import com.tangem.tap.features.onboarding.products.wallet.saltPay.message.SaltPayActivationError
import java.math.BigDecimal

View file

@ -321,14 +321,7 @@ class TokensMiddleware {
}
private fun removeCurrenciesIfNeeded(currencies: List<Currency>) {
if (currencies.isNotEmpty()) {
currencies.forEach { currency ->
store.dispatch(WalletAction.MultiWallet.RemoveWallet(
currency = currency,
fromScreen = AppScreen.AddTokens
))
}
}
if (currencies.isNotEmpty()) store.dispatch(WalletAction.MultiWallet.RemoveWallets(currencies))
}
private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean {

View file

@ -11,7 +11,6 @@ import com.tangem.common.card.Card
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.NotificationAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
@ -76,16 +75,14 @@ sealed class WalletAction : Action {
data class TokenLoaded(
val amount: Amount,
val token: Token,
val blockchain: BlockchainNetwork
val blockchain: BlockchainNetwork,
) : MultiWallet()
data class SelectWallet(val walletData: WalletData?) : MultiWallet()
data class TryToRemoveWallet(val currency: Currency) : MultiWallet()
data class RemoveWallet(
val currency: Currency,
val fromScreen: AppScreen,
) : MultiWallet()
data class RemoveWallet(val currency: Currency) : MultiWallet()
data class RemoveWallets(val currencies: List<Currency>) : MultiWallet()
data class SetPrimaryBlockchain(val blockchain: Blockchain) : MultiWallet()
data class SetPrimaryToken(val token: Token) : MultiWallet()

View file

@ -113,12 +113,7 @@ class MultiWalletMiddleware {
currencyTitle = currency.currencyName,
onOk = {
Analytics.send(ButtonRemoveToken(AnalyticsParam.CurrencyType.Currency(currency)))
store.dispatch(
WalletAction.MultiWallet.RemoveWallet(
currency = currency,
fromScreen = AppScreen.WalletDetails,
),
)
store.dispatch(WalletAction.MultiWallet.RemoveWallet(currency))
store.dispatch(NavigationAction.PopBackTo())
},
),
@ -139,10 +134,17 @@ class MultiWalletMiddleware {
.filter { it.blockchain == currency.blockchain && it.derivationPath == currency.derivationPath }
}
scope.launch { userTokensRepository.saveUserTokens(card, currencies) }
if (action.fromScreen == AppScreen.AddTokens) {
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
}
is WalletAction.MultiWallet.RemoveWallets -> {
val card = globalState.scanResponse?.card.guard {
store.dispatchErrorNotification(TapError.UnsupportedState("card is NULL"))
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
return
}
var currencies = walletState?.currencies ?: emptyList()
currencies = currencies.filterNot { action.currencies.contains(it) }
scope.launch { userTokensRepository.saveUserTokens(card, currencies) }
store.dispatch(WalletAction.MultiWallet.SelectWallet(null))
}
is WalletAction.MultiWallet.ShowWalletBackupWarning -> Unit
is WalletAction.MultiWallet.BackupWallet -> {

View file

@ -16,6 +16,7 @@ import com.tangem.tap.common.analytics.AnalyticsAnOld
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.copyToClipboard
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
@ -25,6 +26,7 @@ import com.tangem.tap.common.redux.AppState
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.TapError
import com.tangem.tap.domain.failedRates
import com.tangem.tap.domain.loadedRates
import com.tangem.tap.features.demo.DemoHelper
@ -39,6 +41,7 @@ import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.WalletData
import com.tangem.tap.features.wallet.redux.WalletState
import com.tangem.tap.features.wallet.redux.WalletStore
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.network.NetworkStateChanged
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
@ -191,7 +194,7 @@ class WalletMiddleware {
Analytics.send(
error,
AnalyticsAnOld.ActionToLog.CreateWallet,
card = store.state.detailsState.scanResponse?.card
card = store.state.detailsState.scanResponse?.card,
)
}
}
@ -252,6 +255,10 @@ class WalletMiddleware {
store.dispatchOpenUrl(action.exploreUrl)
}
is WalletAction.Send -> {
if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) {
store.dispatchErrorNotification(TapError.NoInternetConnection)
return
}
val newAction = prepareSendAction(action.amount, store.state.walletState)
store.dispatch(newAction)
if (newAction is PrepareSendScreen) {

View file

@ -160,6 +160,11 @@ class MultiWalletReducer {
is WalletAction.MultiWallet.RemoveWallet -> {
state.removeWallet(state.getWalletData(action.currency))
}
is WalletAction.MultiWallet.RemoveWallets -> {
var updatedState = state
action.currencies.forEach { updatedState = updatedState.removeWallet(state.getWalletData(it)) }
updatedState
}
is WalletAction.MultiWallet.SetPrimaryBlockchain ->
state.copy(primaryBlockchain = action.blockchain)

View file

@ -24,6 +24,7 @@ class CurrencyIconRequest(
private val currencyTextView: TextView?,
private val token: Token?,
private val blockchain: Blockchain,
private val getLocalImage: Boolean = false,
) {
fun load() {
when {
@ -84,7 +85,7 @@ class CurrencyIconRequest(
crossinline onError: (Blockchain) -> Unit = {},
) {
currencyImageView.loadIcon(
data = getIconUrl(blockchain.toNetworkId()),
data = if (getLocalImage) blockchain.getRoundIconRes() else getIconUrl(blockchain.toNetworkId()),
placeholderRes = blockchain.getRoundIconRes(),
onStart = { onStart(blockchain) },
onSuccess = { onSuccess(blockchain) },

View file

@ -68,6 +68,7 @@ fun CurrencyIconView.load(
currencyTextView = null,
token = null,
blockchain = currency.blockchain,
getLocalImage = true,
).load()
}
}

View file

@ -8,7 +8,6 @@
android:background="@color/backgroundLightGray"
android:clipChildren="false"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
android:orientation="vertical">

View file

@ -153,4 +153,5 @@
<string name="onboarding_subtitle_kyc_retry">Please check you email for further instructions</string>
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
</resources>

View file

@ -153,4 +153,5 @@
<string name="onboarding_subtitle_kyc_retry">Please check you email for further instructions</string>
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
</resources>

View file

@ -153,4 +153,5 @@
<string name="onboarding_subtitle_kyc_retry">Please check you email for further instructions</string>
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
</resources>

View file

@ -151,4 +151,5 @@
<string name="onboarding_subtitle_kyc_retry">Более подробная информация отправлена на ваш адрес электронной почты.</string>
<string name="onboarding_exit_alert_title">Вы хотите выйти из процесса активации?</string>
<string name="onboarding_exit_alert_message">В этом случае вам будет необходимо начать процесс заново.</string>
<string name="error_wrong_wallet_tapped">Вы использовали карту от другого кошелька. Приложите карту, связанную с этим кошельком.</string>
</resources>

View file

@ -153,4 +153,5 @@
<string name="onboarding_subtitle_kyc_retry">Please check you email for further instructions</string>
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_exit_alert_message">In this case, you will need to start from the beginning.</string>
<string name="error_wrong_wallet_tapped">You have used a card from another wallet. Tap the card associated with this wallet</string>
</resources>