diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt index 45d55aac2a..52fa450434 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt @@ -3,11 +3,11 @@ package com.tangem.tap.common.analytics.converters import com.tangem.common.Converter import com.tangem.common.extensions.isZero import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.filters.BasicTopUpFilter import com.tangem.tap.domain.extensions.isMultiwalletAllowed +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.features.wallet.redux.WalletData import com.tangem.tap.features.wallet.redux.WalletState @@ -29,7 +29,9 @@ class BasicSignInEventConverter( currency = cardCurrency, batch = scanResponse.card.batchId, ).apply { - filterData = scanResponse.card.userWalletId.stringValue + filterData = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.stringValue } } } @@ -43,7 +45,7 @@ class BasicTopUpEventConverter( val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null val data = BasicTopUpFilter.Data( - walletId = scanResponse.card.userWalletId.stringValue, + walletId = UserWalletIdBuilder.scanResponse(scanResponse).build()?.stringValue ?: "", cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores), ) diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 16e1bb559a..589597238f 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -10,8 +10,8 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.Address import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder class AdditionalFeedbackInfo { class EmailWalletInfo( @@ -54,7 +54,7 @@ class AdditionalFeedbackInfo { cardFirmwareVersion = data.card.firmwareVersion.stringValue cardIssuer = data.card.issuer.name signedHashesCount = formatSignedHashes(data.card.wallets) - userWalletId = data.card.userWalletId.stringValue + userWalletId = UserWalletIdBuilder.scanResponse(data).build()?.stringValue ?: "" } fun setWalletsInfo(walletManagers: List) { diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt index 871ef1ea5c..28357c1efd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt @@ -57,11 +57,8 @@ val navigationMiddleware: Middleware = { _, state -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Settings.ACTION_BIOMETRIC_ENROLL } - Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { - Settings.ACTION_FINGERPRINT_ENROLL - } else -> { - Settings.ACTION_SETTINGS + Settings.ACTION_SECURITY_SETTINGS } } val intent = Intent(settingsAction).apply { 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 01350c6c63..be8e4f65b2 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -234,6 +234,11 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + fun useBiometricsForAccessCode(): Boolean { + val policy = tangemSdk.config.userCodeRequestPolicy + return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode + } + companion object { val config = Config( linkedTerminal = true, diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt index 4dec0df7d1..a76fbb9d1a 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt @@ -1,6 +1,5 @@ package com.tangem.tap.domain.model -import com.tangem.common.extensions.toHexString import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId @@ -33,23 +32,4 @@ data class UserWallet( val isLocked: Boolean get() = scanResponse.card.wallets.isEmpty() -} - -/** - * !!! Workaround !!! - * - * Calculate same [UserWalletId] for twins instead - * - * TODO: Remove after [REDACTED_JIRA] - * */ -fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { - if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false - if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false - if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - if (scanResponse.secondTwinPublicKey == other.scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) { - return true - } - return false } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt index 6e4d834d38..9148486bc6 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt @@ -1,5 +1,7 @@ package com.tangem.tap.domain.model.builders +import com.tangem.common.card.EllipticCurve +import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result import com.tangem.domain.common.CardDTO @@ -8,7 +10,6 @@ import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper -import com.tangem.domain.common.util.userWalletId import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi import com.tangem.tap.domain.model.UserWallet @@ -40,7 +41,16 @@ class UserWalletBuilder( ProductType.Note -> false ProductType.Twins -> false ProductType.SaltPay -> false - ProductType.Wallet -> !card.isStart2Coin + ProductType.Wallet -> when { + card.isStart2Coin -> false + card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable -> true + else -> { + val cardWallets = card.wallets + require(cardWallets.isNotEmpty()) { "Card wallets must not be empty" } + + cardWallets.first().curve == EllipticCurve.Secp256k1 + } + } } fun backupCardsIds(backupCardsIds: Set?) = this.apply { @@ -49,16 +59,20 @@ class UserWalletBuilder( } } - suspend fun build(): UserWallet { + suspend fun build(): UserWallet? { return with(scanResponse) { - UserWallet( - walletId = card.userWalletId, - name = userWalletName, - artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), - cardsInWallet = backupCardsIds.plus(card.cardId), - scanResponse = this, - isMultiCurrency = isMultiCurrency, - ) + UserWalletIdBuilder.scanResponse(scanResponse) + .build() + ?.let { + UserWallet( + walletId = it, + name = userWalletName, + artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey), + cardsInWallet = backupCardsIds.plus(card.cardId), + scanResponse = this, + isMultiCurrency = isMultiCurrency, + ) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt new file mode 100644 index 0000000000..1fdef7c37c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt @@ -0,0 +1,70 @@ +package com.tangem.tap.domain.model.builders + +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.extensions.hexToBytes +import com.tangem.crypto.Secp256k1 +import com.tangem.domain.common.CardDTO +import com.tangem.domain.common.ProductType +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTangemTwins +import com.tangem.domain.common.extensions.calculateHmacSha256 +import com.tangem.domain.common.util.UserWalletId + +class UserWalletIdBuilder private constructor( + private val publicKey: ByteArray?, + private val pairTwinPublicKey: ByteArray? = null, +) { + fun build(): UserWalletId? { + val seed = if (publicKey != null) { + if (pairTwinPublicKey != null) { + Secp256k1.sum(publicKey, pairTwinPublicKey) + } else { + publicKey + } + } else null + + return seed?.let { + UserWalletId(value = calculateUserWalletId(it)) + } + } + + private fun calculateUserWalletId(seed: ByteArray?): ByteArray? { + val message = MESSAGE_FOR_WALLET_ID.toByteArray() + val keyHash = seed?.calculateSha256() + + return if (keyHash != null) { + message.calculateHmacSha256(keyHash) + } else null + } + + companion object { + private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" + + @Throws(IllegalArgumentException::class) + fun card(card: CardDTO): UserWalletIdBuilder { + require(!card.isTangemTwins) { + "For twin cards use scanResponse to ID calculation" + } + + return UserWalletIdBuilder(findPublicKey(card.wallets)) + } + + fun scanResponse(scanResponse: ScanResponse): UserWalletIdBuilder { + return UserWalletIdBuilder( + publicKey = findPublicKey(scanResponse.card.wallets), + pairTwinPublicKey = when (scanResponse.productType) { + ProductType.Twins -> scanResponse.secondTwinPublicKey?.hexToBytes() + ProductType.Note, + ProductType.Wallet, + ProductType.SaltPay, + -> null + }, + ) + } + + private fun findPublicKey(wallets: List): ByteArray? { + return wallets.firstOrNull() + ?.publicKey + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index efd7dbefb9..2fe9306d6f 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -7,8 +7,8 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechService import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.domain.common.CardDTO -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.AndroidFileReader +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.demo.DemoHelper @@ -29,7 +29,7 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun getUserTokens(card: CardDTO): List = withContext(dispatchers.io) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return@withContext emptyList() if (DemoHelper.isDemoCardId(card.cardId)) { return@withContext loadTokensOffline(card, userId).ifEmpty(::loadDemoCurrencies) } @@ -55,17 +55,15 @@ class UserTokensRepository( // TODO("After adding DI") replace with CoroutineDispatcherProvider suspend fun saveUserTokens(card: CardDTO, tokens: List) = withContext(dispatchers.io) { - val userId = card.userWalletId.stringValue + val userId = getUserWalletId(card) ?: return@withContext val userTokens = tokens.toUserTokensResponse() tangemTechApi.saveUserTokens(userId, userTokens) storageService.saveUserTokens(userId, userTokens) } suspend fun loadBlockchainsToDerive(card: CardDTO): List = withContext(dispatchers.io) { - val blockchainNetworks = loadTokensOffline( - card = card, - userId = card.userWalletId.stringValue, - ).toBlockchainNetworks() + val userId = getUserWalletId(card) ?: return@withContext emptyList() + val blockchainNetworks = loadTokensOffline(card = card, userId = userId).toBlockchainNetworks() if (DemoHelper.isDemoCardId(card.cardId)) { return@withContext blockchainNetworks.ifEmpty(loadDemoCurrencies()::toBlockchainNetworks) @@ -109,6 +107,11 @@ class UserTokensRepository( } } + private fun getUserWalletId(card: CardDTO): String? { + return UserWalletIdBuilder.card(card).build() + ?.stringValue + } + companion object { private const val GROUP_DEFAULT_VALUE = "none" private const val SORT_DEFAULT_VALUE = "manual" diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt index dfd62c05f5..9e7619e7c5 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt @@ -19,7 +19,7 @@ interface UserWalletsListManager { suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult /** - * Save user wallet + * Save provided user wallet and set it as selected * @param userWallet [UserWallet] to save * @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an * already saved card @@ -27,6 +27,17 @@ interface UserWalletsListManager { * */ suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult + /** + * Same as [save] but not change selected user wallet ID + * and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved + * + * Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId] + * @param userWalletId update [UserWallet] with that [UserWalletId] + * @param update lambda that receives stored [UserWallet] and returns updated [UserWallet] + * @return [CompletionResult] of operation with updated [UserWallet] + * */ + suspend fun update(userWalletId: UserWalletId, update: (UserWallet) -> UserWallet): CompletionResult + suspend fun delete(userWalletIds: List): CompletionResult suspend fun clear(): CompletionResult diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index f7781ca770..db1db2ebbd 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -3,7 +3,6 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.model.isTwinnedWith import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.UserWalletsListManager import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey @@ -11,6 +10,7 @@ import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletReposit import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,7 +51,7 @@ internal class BiometricUserWalletsListManager( get() = state.value.isLocked override val hasSavedUserWallets: Boolean - get() = publicInformationRepository.isNotEmpty() + get() = keysRepository.hasSavedEncryptionKeys() override suspend fun unlockWithBiometry(): CompletionResult { return unlockWithBiometryInternal() @@ -85,22 +85,35 @@ internal class BiometricUserWalletsListManager( override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return if (canOverride) { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } else { val isWalletSaved = state.value.userWallets .any { - // Workaround, check [UserWallet.isTwinnedWith] - it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + it.walletId == userWallet.walletId || it.cardsInWallet.contains(userWallet.cardId) } if (isWalletSaved) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - saveInternal(userWallet) + saveInternal(userWallet, changeSelectedUserWallet = true) } } } + override suspend fun update( + userWalletId: UserWalletId, + update: (UserWallet) -> UserWallet, + ): CompletionResult { + return get(userWalletId) + .map { storedUserWallet -> + update(storedUserWallet) + } + .flatMap { updatedUserWallet -> + saveInternal(updatedUserWallet, changeSelectedUserWallet = false) + .map { updatedUserWallet } + } + } + override suspend fun delete(userWalletIds: List): CompletionResult { if (userWalletIds.isEmpty()) { return CompletionResult.Success(Unit) @@ -113,9 +126,12 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.delete(userWalletIds) } .map { state.update { prevState -> + val newUserWallets = prevState.userWallets.filter { it.walletId !in userWalletIds } + prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds }, - userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds }, + userWallets = newUserWallets, + isLocked = newUserWallets.any { it.isLocked }, ) } } @@ -137,27 +153,23 @@ internal class BiometricUserWalletsListManager( } } - private suspend fun saveInternal(userWallet: UserWallet): CompletionResult { - val newEncryptionKeys = state.value.encryptionKeys - .plus(UserWalletEncryptionKey(userWallet)) - .distinctBy { it.walletId } - - return keysRepository.store(newEncryptionKeys) - .doOnSuccess { - state.update { prevState -> - prevState.copy( - encryptionKeys = newEncryptionKeys, - selectedUserWalletId = userWallet.walletId, - ) - } - } + private suspend fun saveInternal( + userWallet: UserWallet, + changeSelectedUserWallet: Boolean, + ): CompletionResult { + return saveEncryptionKeyIfNotNull(userWallet) + .flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = it) } .flatMap { publicInformationRepository.save(userWallet) } - .flatMap { sensitiveInformationRepository.save(userWallet) } .map { selectedUserWalletRepository.set(userWallet.walletId) } .flatMap { loadModels() } .doOnSuccess { state.update { prevState -> prevState.copy( + selectedUserWalletId = if (changeSelectedUserWallet) { + userWallet.walletId + } else { + prevState.selectedUserWalletId + }, isLocked = prevState.userWallets.any { it.isLocked }, ) } @@ -183,6 +195,27 @@ internal class BiometricUserWalletsListManager( } } + private suspend fun saveEncryptionKeyIfNotNull(userWallet: UserWallet): CompletionResult { + val encryptionKey = userWallet.scanResponse.card.encryptionKey + ?.let { UserWalletEncryptionKey(userWallet.walletId, it) } + + return if (encryptionKey != null) { + keysRepository.save(encryptionKey) + .doOnSuccess { + state.update { prevState -> + prevState.copy( + encryptionKeys = prevState.encryptionKeys + .plus(encryptionKey) + .distinctBy { it.walletId }, + ) + } + } + .map { encryptionKey.encryptionKey } + } else { + CompletionResult.Success(data = null) + } + } + private suspend fun loadModels(): CompletionResult { return getSavedUserWallets() .map { userWallets -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt deleted file mode 100644 index e616c48758..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.tap.domain.userWalletList.implementation - -import com.tangem.common.CompletionResult -import com.tangem.common.catching -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.domain.model.UserWallet -import com.tangem.tap.domain.userWalletList.UserWalletsListManager -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class DummyUserWalletsListManager : UserWalletsListManager { - override val userWallets: Flow> - get() = flowOf(emptyList()) - override val selectedUserWallet: Flow - get() = flowOf() - override val selectedUserWalletSync: UserWallet? - get() = null - override val isLocked: Flow - get() = flowOf(true) - override val isLockedSync: Boolean - get() = true - override val hasSavedUserWallets: Boolean - get() = false - - override suspend fun unlockWithBiometry(): CompletionResult { - return CompletionResult.Success(null) - } - - override fun lock() { - /* no-op */ - } - - override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } - - override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun delete(userWalletIds: List): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun clear(): CompletionResult { - return CompletionResult.Success(Unit) - } - - override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return catching { - error("Not implemented") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt index cdf1edbdfb..df0f473bae 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletEncryptionKey.kt @@ -2,19 +2,12 @@ package com.tangem.tap.domain.userWalletList.model import com.squareup.moshi.JsonClass import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey -import com.tangem.tap.domain.model.UserWallet @JsonClass(generateAdapter = true) internal data class UserWalletEncryptionKey( val walletId: UserWalletId, val encryptionKey: ByteArray, ) { - constructor(userWallet: UserWallet) : this( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is UserWalletEncryptionKey) return false diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt index f10e0beeb5..99b62dc99d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt @@ -14,11 +14,11 @@ internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> /** - * Store the encryption keys for user wallets. Biometric authentication not required - * @param encryptionKeys List of encryption keys for user wallets + * Save the encryption key for user wallet. Biometric authentication not required + * @param encryptionKey [UserWalletEncryptionKey] to save * @return [CompletionResult] of operation * */ - suspend fun store(encryptionKeys: List): CompletionResult + suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult /** * Delete encryption keys for user wallets. Biometric authentication not required @@ -32,4 +32,10 @@ internal interface UserWalletsKeysRepository { * @return [CompletionResult] of operation * */ suspend fun clear(): CompletionResult + + /** + * Determine if the user has saved user wallets + * @return [Boolean] true if user has saved wallets + * */ + fun hasSavedEncryptionKeys(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt index f0387d3f0e..8765a6b804 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt @@ -12,6 +12,4 @@ internal interface UserWalletsPublicInformationRepository { suspend fun delete(walletIds: List): CompletionResult suspend fun clear(): CompletionResult - - fun isNotEmpty(): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt index 7e03055150..fdae3e64a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt @@ -7,7 +7,7 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation internal interface UserWalletsSensitiveInformationRepository { - suspend fun save(userWallet: UserWallet): CompletionResult + suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult suspend fun getAll( encryptionKeys: List, ): CompletionResult> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 5e57d10474..11e558535d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -17,6 +17,7 @@ import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext internal class BiometricUserWalletsKeysRepository( @@ -44,10 +45,9 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun store(encryptionKeys: List): CompletionResult { + override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult { return withContext(Dispatchers.IO) { - encryptionKeys.map { storeEncryptionKey(it) } - .fold() + storeEncryptionKey(encryptionKey) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } @@ -77,6 +77,12 @@ internal class BiometricUserWalletsKeysRepository( } } + override fun hasSavedEncryptionKeys(): Boolean { + return runBlocking { + getUserWalletsIds().isNotEmpty() + } + } + private suspend fun getAllInternal(): CompletionResult> { return getUserWalletsIds() .map { userWalletId -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 46a016f8c0..7d1bd9333b 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -72,10 +72,6 @@ internal class DefaultUserWalletsPublicInformationRepository( } } - override fun isNotEmpty(): Boolean { - return secureStorage.get(StorageKey.UserWalletPublicInformation.name)?.isNotEmpty() == true - } - @JvmName("saveWithPublicInformation") private suspend fun save( publicInformation: List, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 5918312b6e..673ec51a10 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -9,13 +9,13 @@ import com.tangem.common.catching import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey import com.tangem.tap.common.extensions.filterNotNull import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository +import com.tangem.tap.domain.userWalletList.utils.encryptionKey import com.tangem.tap.domain.userWalletList.utils.sensitiveInformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -38,11 +38,12 @@ internal class DefaultUserWalletsSensitiveInformationRepository( Cipher.getInstance("$algorithm/$blockMode/$encryptionPadding") } - override suspend fun save(userWallet: UserWallet): CompletionResult { + override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { + if (encryptionKey == null) return CompletionResult.Success(Unit) // Encryption key is null, do nothing return catching { val encryptedSensitiveInformation = userWallet.sensitiveInformation .encode() - .encryptAndStoreIv(userWallet.walletId.stringValue, userWallet.scanResponse.card.encryptionKey) + .encryptAndStoreIv(userWallet.walletId.stringValue, encryptionKey) getAllEncrypted().toMutableMap() .apply { set(userWallet.walletId.stringValue, encryptedSensitiveInformation) } diff --git a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt similarity index 57% rename from domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt rename to app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt index 4dc70fd2bd..d97af026cc 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/CardExtensions.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/UserWalletEncyptionKeyCalculator.kt @@ -1,24 +1,20 @@ -package com.tangem.domain.common.util +package com.tangem.tap.domain.userWalletList.utils import com.tangem.common.extensions.calculateSha256 import com.tangem.domain.common.CardDTO import com.tangem.domain.common.extensions.calculateHmacSha256 -val CardDTO.userWalletId: UserWalletId - get() = UserWalletId(findWalletPublicKey(wallets)) - -val CardDTO.encryptionKey: ByteArray - get() = findWalletPublicKey(wallets) - ?.let { calculateEncryptionKey(it) } - ?: error("Wallet ID not found") +internal val CardDTO.encryptionKey: ByteArray? + get() = findPublicKey(wallets)?.let { calculateEncryptionKey(it) } private fun calculateEncryptionKey(publicKey: ByteArray): ByteArray { val message = MESSAGE_FOR_ENCRYPTION_KEY.toByteArray() val keyHash = publicKey.calculateSha256() + return message.calculateHmacSha256(keyHash) } -private fun findWalletPublicKey(wallets: List): ByteArray? { +private fun findPublicKey(wallets: List): ByteArray? { return wallets.firstOrNull() ?.publicKey } diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt index 5fd2e25234..e000fe1c6d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt @@ -20,6 +20,7 @@ import com.tangem.tap.domain.walletStores.repository.WalletStoresRepository import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.toBlockchainNetworks import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext internal class DefaultWalletCurrenciesManager( @@ -64,7 +65,15 @@ internal class DefaultWalletCurrenciesManager( saveUserCurrencies(card, newCurrencies) } .flatMap { - walletAmountsRepository.updateAmountsForUserWallet( + val updatedBlockchains = updatedBlockchainNetworks + .map { it.blockchain } + val updatedWalletStores = walletStoresRepository.get(userWallet.walletId) + .firstOrNull() + ?.filter { it.blockchain in updatedBlockchains } + ?: return@flatMap CompletionResult.Success(Unit) + + walletAmountsRepository.updateAmountsForWalletStores( + walletStores = updatedWalletStores, userWallet = userWallet, fiatCurrency = appCurrencyProvider(), ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt index 0af5b8f85e..a054ed0ff2 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/WalletAmountsRepository.kt @@ -29,6 +29,12 @@ interface WalletAmountsRepository { fiatCurrency: FiatCurrency, ): CompletionResult + suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult + /** * Fetch wallet amounts and fiat rates then update [com.tangem.tap.domain.walletStores.storage.WalletStoresStorage] * and [com.tangem.tap.domain.walletStores.storage.WalletManagerStorage] with new data diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt index 0c7b19fb4b..a9cb80f66e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt @@ -42,7 +42,8 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -62,7 +63,7 @@ internal class DefaultWalletAmountsRepository( else withContext(Dispatchers.Default) { awaitAll( async { fetchAmountsForUserWallets(userWallets) }, - async { fetchFiatRates(userWallets, fiatCurrency) }, + async { fetchFiatRates(userWallets, walletStores = null, fiatCurrency) }, ) .fold() } @@ -75,46 +76,40 @@ internal class DefaultWalletAmountsRepository( return updateAmountsForUserWallets(listOf(userWallet), fiatCurrency) } + override suspend fun updateAmountsForWalletStores( + walletStores: List, + userWallet: UserWallet, + fiatCurrency: FiatCurrency, + ): CompletionResult { + return if (walletStores.isEmpty()) CompletionResult.Success(Unit) + else withContext(Dispatchers.Default) { + val userWalletId = userWallet.walletId + val scanResponse = userWallet.scanResponse + + awaitAll( + async { fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) }, + async { fetchFiatRates(listOf(userWallet), walletStores, fiatCurrency) }, + ) + .fold() + } + } + override suspend fun updateAmountsForWalletStore( walletStore: WalletStoreModel, userWallet: UserWallet, fiatCurrency: FiatCurrency, - ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId - val scanResponse = userWallet.scanResponse - - awaitAll( - async { - // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] - val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) - }, - async { fetchFiatRates(listOf(userWallet), fiatCurrency) }, - ) - .fold() - } - - private suspend fun fetchAmountsForUserWallets( - userWallets: List, - ): CompletionResult = withContext(Dispatchers.Default) { - userWallets.map { async { fetchAmountsForUserWallet(it) } } - .awaitAll() - .fold() + ): CompletionResult { + return updateAmountsForWalletStores(listOf(walletStore), userWallet, fiatCurrency) } private suspend fun fetchFiatRates( userWallets: List, + walletStores: List?, fiatCurrency: FiatCurrency, ): CompletionResult { - val walletsIds = userWallets.map { it.walletId } - val walletStores = walletsIds - .flatMap { - walletStoresStorage.getAll() - .first() - .getOrElse(it) { emptyList() } - } + val walletStoresInternal = walletStores ?: getWalletStores(userWallets) - val currencies = walletStores + val currencies = walletStoresInternal .asSequence() .flatMap { it.walletsData } .map { it.currency } @@ -124,7 +119,7 @@ internal class DefaultWalletAmountsRepository( return withContext(dispatchers.io) { runCatching { tangemTechApi.getRates(fiatCurrency.code.lowercase(), coinsIds.joinToString(",")) } .onSuccess { - updateWalletStoresWithFiatRates(walletStores = walletStores, fiatRates = it.rates) + updateWalletStoresWithFiatRates(walletStores = walletStoresInternal, fiatRates = it.rates) return@withContext CompletionResult.Success(Unit) } .onFailure { @@ -137,7 +132,6 @@ internal class DefaultWalletAmountsRepository( error, """ Unable to fetch fiat rates - |- User wallets ids: $walletsIds |- Coins ids: $coinsIds """.trimIndent(), ) @@ -149,20 +143,34 @@ internal class DefaultWalletAmountsRepository( } } + private suspend fun fetchAmountsForUserWallets( + userWallets: List, + ): CompletionResult = withContext(Dispatchers.Default) { + userWallets.map { async { fetchAmountsForUserWallet(it) } } + .awaitAll() + .fold() + } + private suspend fun fetchAmountsForUserWallet( userWallet: UserWallet, ): CompletionResult = withContext(Dispatchers.Default) { - val walletId = userWallet.walletId + val userWalletId = userWallet.walletId val scanResponse = userWallet.scanResponse - val walletStores = walletStoresStorage.getAll() - .first() - .getOrElse(walletId) { emptyList() } + val walletStores = getWalletStores(listOf(userWallet)) + fetchAmountForWalletStores(userWalletId, scanResponse, walletStores) + } + + private suspend fun fetchAmountForWalletStores( + userWalletId: UserWalletId, + scanResponse: ScanResponse, + walletStores: List, + ): CompletionResult = coroutineScope { walletStores.map { walletStore -> async { // TODO: Find wallet manager via [com.tangem.tap.domain.walletStores.repository.WalletManagersRepository] val walletManager = walletStore.walletManager - fetchAmountsForWalletStore(walletId, scanResponse, walletStore, walletManager) + fetchAmountsForWalletStore(userWalletId, scanResponse, walletStore, walletManager) } } .awaitAll() @@ -170,7 +178,7 @@ internal class DefaultWalletAmountsRepository( } private suspend fun fetchAmountsForWalletStore( - walletId: UserWalletId, + userWalletId: UserWalletId, scanResponse: ScanResponse, walletStore: WalletStoreModel, walletManager: WalletManager?, @@ -180,11 +188,15 @@ internal class DefaultWalletAmountsRepository( } return when { - hasMissedDerivations -> updateWalletStoreWithMissedDerivation(walletStore) - walletManager == null -> updateWalletStoreWithUnreachable(walletStore) + hasMissedDerivations -> { + updateWalletStoreWithMissedDerivation(walletStore) + } + walletManager == null -> { + updateWalletStoreWithUnreachable(walletStore) + } else -> { withInternetConnection { walletManager.update() } - .map { updateWalletManagerWithAmounts(walletId, walletManager) } + .map { updateWalletManagerWithAmounts(userWalletId, walletManager) } .flatMap { updateWalletStoreWithAmounts( walletStore = walletStore, @@ -242,35 +254,6 @@ internal class DefaultWalletAmountsRepository( return CompletionResult.Success(Unit) } - private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { - return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { - val error = WalletStoresError.NoInternetConnection - Timber.e(error) - CompletionResult.Failure(error) - } else withContext(Dispatchers.IO) { - catching { block() } - } - } - - private suspend fun updateWalletManagerWithAmounts( - walletId: UserWalletId, - walletManager: WalletManager, - ) = withContext(Dispatchers.Default) { - walletManagersStorage.update { prevManagers -> - val newManagersForUserWallet = prevManagers[walletId].orEmpty() - .toMutableList() - .apply { - replaceByOrAdd(walletManager) { - it.wallet.blockchain == it.wallet.blockchain - } - } - - prevManagers.apply { - set(walletId, newManagersForUserWallet) - } - } - } - private suspend fun updateWalletStoreWithError( walletStore: WalletStoreModel, wallet: Wallet, @@ -419,4 +402,44 @@ internal class DefaultWalletAmountsRepository( } } } + + private suspend inline fun withInternetConnection(crossinline block: suspend () -> Unit): CompletionResult { + return if (!NetworkConnectivity.getInstance().isOnlineOrConnecting()) { + val error = WalletStoresError.NoInternetConnection + Timber.e(error) + CompletionResult.Failure(error) + } else withContext(Dispatchers.IO) { + catching { block() } + } + } + + private suspend fun updateWalletManagerWithAmounts( + userWalletId: UserWalletId, + walletManager: WalletManager, + ) = withContext(Dispatchers.Default) { + walletManagersStorage.update { prevManagers -> + val newManagersForUserWallet = prevManagers[userWalletId].orEmpty() + .toMutableList() + .apply { + replaceByOrAdd(walletManager) { + it.wallet.blockchain == it.wallet.blockchain + } + } + + prevManagers.apply { + set(userWalletId, newManagersForUserWallet) + } + } + } + + private suspend fun getWalletStores(userWallets: List): List { + return userWallets + .map { it.walletId } + .flatMap { userWalletId -> + walletStoresStorage.getAll() + .firstOrNull() + ?.get(userWalletId) + .orEmpty() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt index 3de7a3f6b1..6355f2f6d6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletManagersRepository.kt @@ -106,11 +106,7 @@ internal class DefaultWalletManagersRepository( override suspend fun delete(userWalletIds: List): CompletionResult = catching { walletManagersStorage.update { prevManagers -> - prevManagers.apply { - userWalletIds.forEach { userWalletId -> - remove(userWalletId) - } - } + prevManagers.filterKeys { it !in userWalletIds } as HashMap> } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt index 445ff655f2..2aa7ffcf2d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/utils/WalletStoreOperations.kt @@ -62,9 +62,11 @@ internal fun WalletStoreModel.updateWithSelf( ): WalletStoreModel { val oldStore = this return oldStore.copy( - walletManager = newWalletStore.walletManager, - walletRent = newWalletStore.walletRent, + derivationPath = newWalletStore.derivationPath, walletsData = oldStore.walletsData.updateWithSelf(newWalletStore.walletsData), + walletRent = newWalletStore.walletRent, + blockchainNetwork = newWalletStore.blockchainNetwork, + walletManager = newWalletStore.walletManager, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 319071642c..9fe984d9c9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -4,10 +4,8 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.common.extensions.toHexString import com.tangem.common.flatMap import com.tangem.domain.common.TapWorkarounds.isTangemTwins -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Settings @@ -20,6 +18,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.model.builders.UserWalletBuilder +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder 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 @@ -27,6 +26,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userTokensRepository import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager import com.tangem.wallet.R @@ -79,20 +79,16 @@ class DetailsMiddleware { } DetailsAction.ScanCard -> { scope.launch { - tangemSdkManager.scanCard(allowRequestAccessCodeFromRepository = true) - .doOnSuccess { card -> - val isSameWallet = state.scanResponse?.card?.userWalletId - ?.equals(card.userWalletId) - ?: false + tangemSdkManager.scanProduct(userTokensRepository) + .doOnSuccess { scanResponse -> + val currentUserWalletId = state.scanResponse + ?.let { UserWalletIdBuilder.scanResponse(it).build() } + val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse) + .build() + val isSameWallet = currentUserWalletId == scannedUserWalletId - // !!! Workaround !!! - // TODO: Remove after [REDACTED_JIRA] - val isTwinned = card.wallets.firstOrNull()?.publicKey?.toHexString() - ?.equals(state.scanResponse?.secondTwinPublicKey) - ?: false - - if (isSameWallet || isTwinned) { - store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(card)) + if (isSameWallet) { + store.dispatchOnMain(DetailsAction.PrepareCardSettingsData(scanResponse.card)) } else { store.dispatchDialogShow( AppDialog.SimpleOkDialogRes( @@ -122,8 +118,10 @@ class DetailsMiddleware { is DetailsAction.ResetToFactory.Proceed -> { val card = store.state.detailsState.cardSettingsState?.card ?: return scope.launch { + val userWalletId = UserWalletIdBuilder.card(card).build() + tangemSdkManager.resetToFactorySettings(card.cardId) - .flatMap { userWalletsListManager.delete(listOf(card.userWalletId)) } + .flatMap { userWalletsListManager.delete(listOfNotNull(userWalletId)) } .flatMap { tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId)) } .doOnSuccess { Analytics.send(Settings.CardSettings.FactoryResetFinished()) @@ -275,12 +273,9 @@ class DetailsMiddleware { private suspend fun saveCurrentWallet(state: DetailsState) { val scanResponse = state.scanResponse ?: return - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return userWalletsListManager.save(userWallet) - .doOnFailure { error -> - Timber.e(error, "Wallet saving failed") - } .doOnSuccess { Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) @@ -296,6 +291,9 @@ class DetailsMiddleware { store.onUserWalletSelected(userWallet) } + .doOnFailure { error -> + Timber.e(error, "Unable to save user wallet") + } } private suspend fun deleteSavedWallets() { @@ -315,6 +313,9 @@ class DetailsMiddleware { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved wallets") + } } private fun saveAccessCodes(state: DetailsState) { @@ -353,6 +354,9 @@ class DetailsMiddleware { ) } } + .doOnFailure { error -> + Timber.e(error, "Unable to delete saved access codes") + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt index d040111cdf..d6c2454283 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt @@ -12,7 +12,6 @@ import com.tangem.tap.domain.extensions.signedHashesCount import com.tangem.tap.preferencesStorage import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import com.tangem.tap.userWalletsListManager import org.rekotlin.Action import java.util.* @@ -58,7 +57,7 @@ private fun handlePrepareScreen( createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup, appCurrency = store.state.globalState.appCurrency, isBiometricsAvailable = tangemSdkManager.canUseBiometry, - saveWallets = userWalletsListManager.hasSavedUserWallets, + saveWallets = preferencesStorage.shouldSaveUserWallets, saveAccessCodes = preferencesStorage.shouldSaveAccessCodes, ) } diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt index fd0ad8a3db..367ea44ac5 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/CardSettingsViewModel.kt @@ -8,6 +8,7 @@ import com.tangem.tap.common.analytics.events.Settings import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.details.redux.CardSettingsState import com.tangem.tap.features.details.redux.DetailsAction +import com.tangem.wallet.R import org.rekotlin.Store class CardSettingsViewModel(private val store: Store) { diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt index 05a69049a9..02095d290a 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material.IconToggleButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -37,6 +38,7 @@ fun ResetCardScreen(state: ResetCardScreenState, onBackPressed: () -> Unit) { SettingsScreensScaffold( content = { ResetCardView(state = state) }, onBackClick = onBackPressed, + backgroundColor = Color.Transparent, ) } diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 0c5b990b06..fdacaa5bd1 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -93,7 +93,7 @@ private fun readCard() = scope.launch { onSuccess = { scanResponse -> scope.launch { if (preferencesStorage.shouldSaveUserWallets) { - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@launch userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt index 4635716436..b56ddaf6cd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -11,8 +11,8 @@ import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.common.extensions.dispatchShare import com.tangem.tap.common.extensions.dispatchToastNotification +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.AppDialog -import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.store import com.tangem.wallet.R @@ -62,26 +62,12 @@ class AddressInfoBottomSheetDialog( Analytics.send(Token.Recieve.ButtonShareAddress()) store.dispatchShare(data.shareUrl) } - tvReceiveMessage.text = getQRReceiveMessage(tvReceiveMessage.context, stateDialog.currency) - } -} - -fun getQRReceiveMessage(context: Context, currency: Currency): String { - return when (currency) { - is Currency.Blockchain -> { - context.getString( - R.string.address_qr_code_message_format, - currency.blockchain.fullName, - currency.currencySymbol, - ) - } - is Currency.Token -> { - context.getString( - R.string.address_qr_code_message_token_format, - currency.token.name, - currency.currencySymbol, - currency.blockchain.fullName, - ) - } + val blockchain = stateDialog.currency.blockchain + tvReceiveMessage.text = tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 9b5c5e9958..775dca1ed3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -70,7 +70,10 @@ class OnboardingHelper { // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { + store.onCardScanned(scanResponse) + delay(timeMillis = 1_200) + store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( scanResponse = scanResponse, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index bc94264e05..1a5b2840d3 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -6,7 +6,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.extensions.withMainContext -import com.tangem.domain.common.util.userWalletId import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -25,6 +24,7 @@ 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.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.Currency @@ -156,7 +156,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) { TwinCardsStep.CreateFirstWallet -> { scope.launch { userWalletsListManager.delete( - listOf(getScanResponse().card.userWalletId), + listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()), ) } } diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 106ebfa9af..60bc2e076e 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -88,7 +88,7 @@ internal class SaveWalletMiddleware { scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) - .build() + .build() ?: return@launch val isFirstSavedWallet = !userWalletsListManager.hasSavedUserWallets diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index b07a4fd3ca..af6ab1607f 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -149,7 +149,7 @@ class TokensMiddleware { && blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty() ) { store.dispatchDebugErrorNotification("Nothing to save") - store.dispatch(NavigationAction.PopBackTo()) + store.dispatchOnMain(NavigationAction.PopBackTo()) return@launch } @@ -281,13 +281,16 @@ class TokensMiddleware { ) { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - val updatedUserWallet = selectedUserWallet.copy( - scanResponse = scanResponse, - ) - scope.launch { - userWalletsListManager.save(updatedUserWallet, canOverride = true) - .flatMap { + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, + ) + .flatMap { updatedUserWallet -> walletCurrenciesManager.addCurrencies( userWallet = updatedUserWallet, currenciesToAdd = currencyList, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index 44f184a4c1..49e4285064 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchErrorNotification +import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.global.GlobalState @@ -180,21 +181,25 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard( - selectedWallet: UserWallet, + selectedUserWallet: UserWallet, state: WalletState?, - ) = scope.launch { + ) = scope.launch(Dispatchers.Default) { Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( - cardId = selectedWallet.cardId, + cardId = selectedUserWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, ) { scanResponse -> - val userWallet = selectedWallet.copy( - scanResponse = scanResponse, + userWalletsListManager.update( + userWalletId = selectedUserWallet.walletId, + update = { userWallet -> + userWallet.copy( + scanResponse = scanResponse, + ) + }, ) - - userWalletsListManager.save(userWallet, canOverride = true) - .doOnSuccess { - store.state.globalState.tapWalletManager.loadData(userWallet, refresh = true) + .doOnSuccess { updatedUserWallet -> + store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList())) + store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true) } } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index 222f7b7fa1..88916c3e2b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -306,7 +306,7 @@ class WalletMiddleware { val reduxWalletStores = wallStores.mapToReduxModels(state.isMultiwalletAllowed) store.dispatchOnMain( WalletAction.WalletStoresChanged.UpdateWalletStores( - reduxWalletStores = reduxWalletStores.toList(), + reduxWalletStores = reduxWalletStores, ), ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt index 729d99f960..afb523a5ca 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/MultiWalletReducer.kt @@ -36,7 +36,7 @@ class MultiWalletReducer { fun reduce(action: WalletAction.MultiWallet, state: WalletState): WalletState { return when (action) { is WalletAction.MultiWallet.AddBlockchains -> { - val walletStores: List = action.blockchains.mapNotNull { blockchain -> + val walletStores: List = action.blockchains.map { blockchain -> val walletManager = action.walletManagers.firstOrNull { it.wallet.blockchain == blockchain.blockchain && (it.wallet.publicKey.derivationPath?.rawPath == blockchain.derivationPath) @@ -70,10 +70,10 @@ class MultiWalletReducer { ) } - val selectedCurrency = if (!state.isMultiwalletAllowed) { - walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency - } else { + val selectedCurrency = if (state.isMultiwalletAllowed) { state.selectedCurrency + } else { + walletStores.firstOrNull()?.walletsData?.firstOrNull()?.currency } state.copy( walletsStores = walletStores, 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 254fe48e3b..2505702c20 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 @@ -20,7 +20,6 @@ import com.tangem.tap.domain.extensions.isMultiwalletAllowed import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.Currency -import com.tangem.tap.features.wallet.models.TotalBalance import com.tangem.tap.features.wallet.models.WalletRent import com.tangem.tap.features.wallet.redux.AddressData import com.tangem.tap.features.wallet.redux.Artwork @@ -36,7 +35,6 @@ import com.tangem.tap.features.wallet.redux.replaceSomeWalletsData import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.store import org.rekotlin.Action import timber.log.Timber import java.math.BigDecimal @@ -344,8 +342,9 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS card.settings.isBackupAllowed && card.backupStatus == CardDTO.BackupStatus.NoBackup, walletCardsCount = card.findCardsCount(), + walletsStores = newState.walletsStores, totalBalance = if (isMultiCurrency) { - TotalBalance(ProgressState.Loading, BigDecimal.ZERO, store.state.globalState.appCurrency) + newState.totalBalance } else { null }, @@ -362,13 +361,13 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS ) } is WalletAction.LoadData.Success -> { - val selectedCurrency = if (!newState.isMultiwalletAllowed) { + val selectedCurrency = if (newState.isMultiwalletAllowed) { + newState.selectedCurrency + } else { newState.walletsStores.firstOrNull() ?.walletsData ?.firstOrNull() ?.currency - } else { - newState.selectedWalletData?.currency } newState = newState.copy( diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 0795efcea9..fcbe06954f 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -36,7 +36,6 @@ import com.tangem.tap.common.extensions.toQrCode import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork -import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.models.PendingTransaction import com.tangem.tap.features.wallet.redux.ErrorType @@ -132,12 +131,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), override fun onStop() { super.onStop() store.unsubscribe(this) - } - - override fun onDestroy() { walletDataWatcher.clear() walletStateWatcher.clear() - super.onDestroy() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -328,8 +323,21 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), ) } ivQrCode.setImageBitmap(state.walletAddresses.selectedAddress.shareUrl.toQrCode()) - tvReceiveMessage.text = - getQRReceiveMessage(tvReceiveMessage.context, state.currency) + + tvReceiveMessage.text = when (val currency = state.currency) { + is Currency.Blockchain -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.blockchain.fullName, + currency.currencySymbol, + currency.blockchain.fullName, + ) + is Currency.Token -> tvReceiveMessage.getString( + id = R.string.address_qr_code_message_format, + currency.token.name, + currency.currencySymbol, + currency.blockchain.fullName, + ) + } } } 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 c0bde194bf..f01805dd39 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 @@ -1,5 +1,6 @@ package com.tangem.tap.features.wallet.ui +import android.content.Context import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -9,6 +10,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.transition.TransitionInflater @@ -16,6 +18,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import coil.load import coil.size.Scale import com.tangem.core.ui.fragments.setStatusBarColor +import com.tangem.core.ui.utils.OneTouchClickListener import com.tangem.domain.common.TapWorkarounds.isSaltPay import com.tangem.tap.MainActivity import com.tangem.tap.common.analytics.Analytics @@ -59,6 +62,13 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber() + override fun onAttach(context: Context) { + super.onAttach(context) + activity?.lifecycleScope?.launchWhenCreated { + viewModel.launch() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -81,7 +91,6 @@ class WalletFragment : Fragment(R.layout.fragment_wallet), StoreSubscriber - walletStoresManager.get(selectedWallet.walletId) + .map { it.walletId } + .distinctUntilChanged() + .flatMapLatest { selectedUserWalletId -> + walletStoresManager.get(selectedUserWalletId) } .onEach { walletStores -> store.dispatch(WalletAction.WalletStoresChanged(walletStores)) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index ed9576d384..1e96cb230b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.tangem.tap.common.extensions.beginDelayedTransition import com.tangem.tap.common.extensions.fitChipsByGroupWidth import com.tangem.tap.common.extensions.getQuantityString +import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState @@ -65,7 +66,7 @@ class SingleWalletView : WalletView() { setupTwinCards(state.twinCardsState, binding) setupButtons(state.primaryWallet, binding, state.isExchangeServiceFeatureOn) - setupAddressCard(state.primaryWallet, binding) + setupAddressCard(state, binding) showPendingTransactionsIfPresent(state.primaryWallet.pendingTransactions) setupBalance(state, state.primaryWallet) } @@ -152,35 +153,50 @@ class SingleWalletView : WalletView() { } } - private fun setupAddressCard(state: WalletData, binding: FragmentWalletBinding) = with(binding.lAddress) { - if (state.walletAddresses != null && state.currency is Currency.Blockchain) { + private fun setupAddressCard(state: WalletState, binding: FragmentWalletBinding) = with(binding.lAddress) { + val primaryWallet = state.primaryWallet + if (primaryWallet?.walletAddresses != null && primaryWallet.currency is Currency.Blockchain) { binding.lAddress.root.show() - if (state.shouldShowMultipleAddress()) { + if (primaryWallet.shouldShowMultipleAddress()) { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(state.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener - val type = MultipleAddressUiHelper.idToType(checkedId, state.currency.blockchain) + val type = MultipleAddressUiHelper.idToType(checkedId, primaryWallet.currency.blockchain) type?.let { store.dispatch(WalletAction.ChangeSelectedAddress(type)) } } } else { chipGroupAddressType.hide() } - tvAddress.text = state.walletAddresses.selectedAddress.address + tvAddress.text = primaryWallet.walletAddresses.selectedAddress.address tvExplore.setOnClickListener { store.dispatch( WalletAction.ExploreAddress( - state.walletAddresses.selectedAddress.exploreUrl, + primaryWallet.walletAddresses.selectedAddress.exploreUrl, fragment!!.requireContext(), ), ) } + setupCardInfo(state) } else { binding.lAddress.root.hide() } } + + private fun setupCardInfo(state: WalletState) { + val textView = binding?.lAddress?.tvInfo + val blockchain = state.primaryWallet?.currency?.blockchain + if (textView != null && blockchain != null) { + textView.text = textView.getString( + id = R.string.address_qr_code_message_format, + blockchain.fullName, + blockchain.currency, + blockchain.fullName, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt index b6b31419de..4772b501d7 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.walletSelector.redux import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap @@ -19,6 +20,7 @@ import com.tangem.tap.domain.model.UserWallet import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.scanCard.ScanCardProcessor +import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -131,21 +133,46 @@ internal class WalletSelectorMiddleware { private fun addWallet() = scope.launch { Analytics.send(MyWallets.Button.ScanNewCard) - scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() - userWalletsListManager.save(userWallet) - .doOnFailure { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - } - .doOnSuccess { - Analytics.send(MyWallets.CardWasScanned) + // Update access code policy for access code saving when a card was scanned + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + ) + ScanCardProcessor.scan( + onSuccess = { scanResponse -> + saveUserWalletAndPopBackToWalletScreen(scanResponse) + .doOnFailure { error -> + // Rollback policy if card saving was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + } + }, + onFailure = { error -> + // Rollback policy if card scanning was failed + tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) + }, + onWalletNotCreated = { + // No need to rollback policy, continue with the policy set before the card scan + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + }, + ) + } - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } - } + private suspend fun saveUserWalletAndPopBackToWalletScreen(scanResponse: ScanResponse): CompletionResult { + val userWallet = UserWalletBuilder(scanResponse).build() + ?: return CompletionResult.Failure(TangemSdkError.WalletIsNotCreated()) + + return userWalletsListManager.save(userWallet) + .doOnSuccess { + Analytics.send(MyWallets.CardWasScanned) + + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } } private fun selectWallet(userWalletId: UserWalletId) { @@ -212,9 +239,7 @@ internal class WalletSelectorMiddleware { Analytics.send(MyWallets.Button.EditWalletTapped) scope.launch { - userWalletsListManager.get(userWalletId) - .map { it.copy(name = newName) } - .flatMap { userWalletsListManager.save(it, canOverride = true) } + userWalletsListManager.update(userWalletId) { it.copy(name = newName) } .doOnFailure { error -> store.dispatchOnMain(WalletSelectorAction.HandleError(error)) } @@ -252,43 +277,32 @@ internal class WalletSelectorMiddleware { .flatMap { walletStoresManager.delete(userWalletsIds) } .flatMap { deleteAccessCodes(userWalletsIds) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWalletSync + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync when { - selectedWallet == null -> { + selectedUserWallet == null -> { store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) } - currentSelectedWalletId != selectedWallet.walletId -> { - store.onUserWalletSelected(selectedWallet) + + currentSelectedWalletId != selectedUserWallet.walletId -> { + store.onUserWalletSelected(selectedUserWallet) } } } } - private suspend inline fun scanCardInternal( - crossinline onCardScanned: suspend (ScanResponse) -> Unit, - ) { - ScanCardProcessor.scan( - onSuccess = { - onCardScanned(it) - }, - onFailure = { error -> - store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) - }, - onWalletNotCreated = { - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) - store.dispatchOnMain(NavigationAction.PopBackTo()) - }, - ) - } - private suspend fun deleteAccessCodes(userWalletsIds: List): CompletionResult { - val cardsIds = userWalletsListManager.userWallets.firstOrNull().orEmpty() - .asSequence() - .filter { it.walletId in userWalletsIds } - .flatMap { it.cardsInWallet } + val cardsIds = userWalletsListManager.userWallets.firstOrNull() + ?.asSequence() + ?.filter { it.walletId in userWalletsIds } + ?.flatMap { it.cardsInWallet } + ?.toSet() - return tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + return if (cardsIds.isNullOrEmpty()) { + CompletionResult.Success(Unit) + } else { + tangemSdkManager.deleteSavedUserCodes(cardsIds.toSet()) + } } private suspend fun UserWalletModel.updateWalletStoresAndCalculateFiatBalance( diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 1192b37c4a..52a99013e3 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -38,7 +38,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit editingUserWalletsIds.isNotEmpty() && !editingUserWalletsIds.contains(userWalletId) -> { editWallet(userWalletId) } @@ -52,7 +51,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber - store.dispatch(WalletSelectorAction.RenameWallet(editedWalletId, newName)) + store.dispatch(WalletSelectorAction.RenameWallet(editedUserWalletId, newName)) stateInternal.update { prevState -> prevState.copy( renameWalletDialog = null, @@ -137,11 +136,6 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber appState.skip { old, new -> old.walletSelectorState == new.walletSelectorState } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 7b0f37e36e..e7cb2e01e7 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -71,7 +71,7 @@ internal class WelcomeMiddleware { private fun proceedWithCard(state: WelcomeState) = scope.launch { scanCardInternal { scanResponse -> - val userWallet = UserWalletBuilder(scanResponse).build() + val userWallet = UserWalletBuilder(scanResponse).build() ?: return@scanCardInternal userWalletsListManager.save(userWallet, canOverride = true) .doOnFailure { error -> diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt index 5d53a5dadd..926eeb68b5 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -8,12 +8,12 @@ import com.tangem.domain.common.CardDTO import com.tangem.domain.common.TapWorkarounds.derivationStyle import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId -import com.tangem.domain.common.util.userWalletId import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.Currency.NativeToken import com.tangem.lib.crypto.models.Currency.NonNativeToken import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.redux.WalletAction import org.rekotlin.Action @@ -54,7 +54,12 @@ class UserWalletManagerImpl( } override fun getWalletId(): String { - return appStateHolder.getActualCard()?.userWalletId?.stringValue ?: "" + return appStateHolder.getActualCard()?.let { + UserWalletIdBuilder.card(it) + .build() + ?.stringValue + } + ?: "" } override suspend fun isTokenAdded(currency: Currency): Boolean { diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml index ca04440067..eadb8f7f36 100644 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ b/app/src/main/res/layout/dialog_onboarding_address_info.xml @@ -36,7 +36,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imv_qr_code" - tools:text="@string/address_qr_code_message_token_format" /> + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> + + + tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." /> diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 524e5e5277..2f14017e3e 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -63,7 +63,7 @@ object Versions { const val tangemBlockchainSdk = "develop-142" // const val tangemBlockchainSdk = "0.0.1" - const val tangemCardSgk = "develop-178" + const val tangemCardSgk = "develop-179" // endregion Tangem // region Testing diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 37d63b9b52..0736fbbc65 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… @@ -355,8 +353,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. OK, ich hab\'s! diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index f65d8b25c1..54f1eaf20d 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… @@ -355,8 +353,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, je l\'ai! diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index f92dde614e..185618e41e 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… @@ -355,8 +353,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, ho capito! diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 73375dc62d..4e96b15a4c 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 карты - 2 карты Доставка Бесплатно У меня есть промо-код… @@ -208,8 +206,8 @@ Ваши токены Другие токены Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. - Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. - Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Код ошибки: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. Персональный код скопирован! Купи Tangem Wallet со скидкой!\n%s Сеть %s использует концепцию экзистенциального депозита. Если баланс вашего счета опустится ниже %s, он будет деактивирован, а все оставшиеся средства будут уничтожены. @@ -355,8 +353,7 @@ Успешно! Ваша карта активирована и готова к использованию Баланс - Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. - Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) в сети %s на этот адрес. Использование другой сети может привести к утрате средств. Если процесc создания кошелька каким-либо образом прервется, вам придется начинать сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Понятно! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e7edae9700..42bbac57a2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1,8 +1,6 @@ Tangem Wallet - 3 cards - 2 cards Shipping Free I have a promo code… @@ -33,7 +31,7 @@ Search tokens You are currently running in Demo mode. All funds are not real. This feature is disabled in Demo mode - Not enough funds for fee on your %s wallet to send a transaction. Top up your %s wallet first. + Not enough funds for fee in your %s wallet to send a transaction. Top up your %s wallet first. Available networks %s network not found. Please, add it first and try again. Attention @@ -208,8 +206,8 @@ Your tokens Other tokens Failed to load the information about the referral program. Please try again later. - Failed to load the information about the referral program. Reason: %s. Please try again later. - Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Failed to load the information about the referral program. Error code: %s. Please try again later. + Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support. Personal code copied! Buy Tangem Wallet with discount!\n%s %s network has a concept of Existential Deposit. If your account drops below %s it will be deactivated and any remaining funds will be destroyed. @@ -355,8 +353,7 @@ Success! Your card is activated and ready to be used Balance - Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. - Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. Ok, Got it! diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt new file mode 100644 index 0000000000..210c57805c --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/OneTouchClickListener.kt @@ -0,0 +1,25 @@ +package com.tangem.core.ui.utils + +import android.os.SystemClock +import android.view.View + +/** + * Implementation of click listener for preventing multiple click events + * + * @property action action that called when a view has been clicked + */ +class OneTouchClickListener(private val action: () -> Unit) : View.OnClickListener { + + private var lastClickTimeMs: Long = 0L + + override fun onClick(v: View?) { + if (SystemClock.elapsedRealtime() - lastClickTimeMs > CLICK_DELAY_MS) { + lastClickTimeMs = SystemClock.elapsedRealtime() + action() + } + } + + companion object { + private const val CLICK_DELAY_MS = 500L + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt index 4e83c913ca..abf878da69 100644 --- a/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt +++ b/domain/src/main/java/com/tangem/domain/common/util/UserWalletId.kt @@ -1,17 +1,15 @@ package com.tangem.domain.common.util -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString -import com.tangem.domain.common.extensions.calculateHmacSha256 class UserWalletId( val stringValue: String, ) { val value = stringValue.hexToBytes() - constructor(walletPublicKey: ByteArray?) : this( - stringValue = walletPublicKey?.let { calculateUserWalletId(it).toHexString() } ?: "", + constructor(value: ByteArray?) : this( + stringValue = value?.toHexString() ?: "", ) override fun equals(other: Any?): Boolean { @@ -32,12 +30,4 @@ class UserWalletId( "UserWalletId(${take(3)}...${takeLast(3)})" } } -} - -private fun calculateUserWalletId(publicKey: ByteArray): ByteArray { - val message = MESSAGE_FOR_WALLET_ID.toByteArray() - val keyHash = publicKey.calculateSha256() - return message.calculateHmacSha256(keyHash) -} - -private const val MESSAGE_FOR_WALLET_ID = "UserWalletID" \ No newline at end of file +} \ No newline at end of file