diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index e19202bf4f..a42cc3568e 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -14,6 +14,11 @@ sealed class MainScreen( class CardWasScanned : MainScreen("Card Was Scanned") class ButtonMyWallets : MainScreen("Button - My Wallets") + class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( + event = "Enable Biometric", + params = mapOf("State" to state.value), + ) + class MainCurrencyChanged(currencyType: AnalyticsParam.CurrencyType) : MainScreen( event = "Main Currency Changed", params = mapOf("Currency Type" to currencyType.value), diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt new file mode 100644 index 0000000000..0fd08ddd88 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.common.analytics.events + +sealed class MyWallets( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent("My Wallets", event, params) { + + object MyWalletsScreenOpened : MyWallets(event = "My Wallets Screen Opened") + object CardWasScanned : MyWallets(event = "Card Was Scanned") + object WalletUnlockTapped : MyWallets(event = "Wallet Unlock Tapped") + + object Button { + object ScanNewCard : MyWallets(event = "Button - Scan New Card") + object UnlockWithBiometrics : MyWallets(event = "Button - Unlock all with Face ID") + object EditWalletTapped : MyWallets(event = "Button - Edit Wallet Tapped") + object DeleteWalletTapped : MyWallets(event = "Button - Delete Wallet Tapped") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index 5634c25afa..92f55074e9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -73,4 +73,9 @@ sealed class Onboarding( class ClaimScreenOpened : Onboarding("Onboarding", "Claim screen opened") class ButtonClaim : Onboarding("Onboarding", "Button - Claim") class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim was successfully") + class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding( + category = "Onboarding / Biometric", + event = "Enable Biometric", + params = mapOf("State" to state.value), + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 38725048a7..728662bdb9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -58,16 +58,16 @@ sealed class Settings( params: Map = mapOf(), ) : Settings("Settings / App Settings", event, params) { - sealed class FaceIDSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( - event = "Face ID Switcher Changed", + class SaveWalletSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( + event = "Save Wallet Switcher Changed", params = mapOf("State" to state.value), ) - sealed class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( + class SaveAccessCodeSwitcherChanged(state: AnalyticsParam.OnOffState) : CardSettings( event = "Save Access Code Switcher Changed", params = mapOf("State" to state.value), ) - class ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication") + object ButtonEnableBiometricAuthentication : AppSettings("Button - Enable Biometric Authentication") } } \ No newline at end of file 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 90d6b880da..c8457793e5 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 @@ -32,10 +32,8 @@ val navigationMiddleware: Middleware = { _, state -> AppScreen.Home, AppScreen.Welcome, -> { - navState?.activity?.get()?.popBackTo(screen, action.inclusive) - if (navState?.backStack?.contains(screen) != true) { - store.dispatchOnMain(NavigationAction.NavigateTo(screen)) - } + navState?.activity?.get()?.popBackTo(screen, inclusive = true) + store.dispatchOnMain(NavigationAction.NavigateTo(screen)) } else -> { navState?.activity?.get()?.popBackTo(screen, action.inclusive) 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 906a0f1306..81ddf880c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -38,7 +38,6 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tokens.UserTokensRepository -import com.tangem.tap.domain.userWalletList.di.USER_WALLETS_BIOMETRIC_KEY_NAME import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive @@ -119,16 +118,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co .map { CardDTO(it) } } - suspend fun unlockBiometricKeys(): CompletionResult { - return biometricManager.authenticate( - mode = BiometricManager.AuthenticationMode.Keys( - USER_WALLETS_BIOMETRIC_KEY_NAME, - tangemSdk.config.userCodesBiometricKeyName, - ), - ) - .map { /* no-op */ } - } - suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult { return createUserCodeRepository().save( cardIds = cardsIds, @@ -137,20 +126,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co stringValue = accessCode, ), ) - .map { - biometricManager.unauthenticate( - keyName = tangemSdk.config.userCodesBiometricKeyName, - ) - } } suspend fun clearSavedUserCodes(): CompletionResult { return createUserCodeRepository().clear() - .map { - biometricManager.unauthenticate( - keyName = tangemSdk.config.userCodesBiometricKeyName, - ) - } } suspend fun setPasscode(cardId: String?): CompletionResult { @@ -229,7 +208,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { - return context.getString(stringResId, formatArgs) + return context.getString(stringResId, *formatArgs) } fun setAccessCodeRequestPolicy( 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 a4433cb32a..8c3403ede9 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,5 +1,6 @@ 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 @@ -12,6 +13,9 @@ import com.tangem.domain.common.util.UserWalletId * @param scanResponse [ScanResponse] of primary user's wallet card. * TODO: Replace with [com.tangem.domain.common.CardDTO] * @property cardId ID of user's wallet primary card + * @property hasAccessCode Indicates if the user's wallet primary card has access code + * @property isLocked Indicates if this primary card has no currency wallets + * @property isSaved Indicates if this user wallet is saved * */ data class UserWallet( val name: String, @@ -23,5 +27,27 @@ data class UserWallet( val cardId: String get() = scanResponse.card.cardId + val hasAccessCode: Boolean + get() = scanResponse.card.isAccessCodeSet + + val isLocked: Boolean + get() = scanResponse.card.wallets.isEmpty() + internal var isSaved: Boolean = true +} + +/** + * !!! Workaround !!! + * + * Calculate same [UserWalletId] for twins instead + * */ +fun UserWallet.isTwinnedWith(other: UserWallet): Boolean { + if (!scanResponse.isTangemTwins()) 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/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index a219814de1..98ce39259e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -59,7 +59,7 @@ class CreateProductWalletTask( private val type: ProductType, ) : CardSessionRunnable { - override val allowsAccessCodeFromRepository: Boolean = false + override val allowsRequestAccessCodeFromRepository: Boolean = false override fun run( session: CardSession, diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 537500d0b4..e20a6cc197 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -45,7 +45,7 @@ class ScanProductTask( private val additionalBlockchainsToDerive: Collection? = null, ) : CardSessionRunnable { - override val allowsAccessCodeFromRepository: Boolean + override val allowsRequestAccessCodeFromRepository: Boolean get() = !additionalBlockchainsToDerive.isNullOrEmpty() override fun run( diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt index d812507524..ae9a72152a 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt @@ -4,13 +4,12 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable -import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask import com.tangem.operations.wallet.PurgeWalletCommand -class CreateFirstTwinWalletTask : CardSessionRunnable { +class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable { override fun run( session: CardSession, callback: (result: CompletionResult) -> Unit, @@ -18,8 +17,11 @@ class CreateFirstTwinWalletTask : CardSessionRunnable { val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { - if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.Second) { - callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.First))) + val requiredTwinCardNumber = TwinsHelper.getTwinCardNumber(firstCardId) + if (requiredTwinCardNumber != TwinsHelper.getTwinCardNumber(card.cardId)) { + requiredTwinCardNumber?.let { + callback(CompletionResult.Failure(WrongTwinCard(it))) + } return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt index 8950bc0a02..57b66d7a62 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt @@ -7,7 +7,6 @@ import com.tangem.common.card.EllipticCurve import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.extensions.hexToBytes -import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask @@ -15,6 +14,7 @@ import com.tangem.operations.wallet.PurgeWalletCommand class CreateSecondTwinWalletTask( private val firstPublicKey: String, + private val firstCardId: String, private val issuerKeys: KeyPair, private val preparingMessage: Message, private val creatingWalletMessage: Message, @@ -24,8 +24,11 @@ class CreateSecondTwinWalletTask( val card = session.environment.card val publicKey = card?.wallets?.firstOrNull()?.publicKey if (publicKey != null) { - if (TwinsHelper.getTwinCardNumber(card.cardId) == TwinCardNumber.First) { - callback(CompletionResult.Failure(WrongTwinCard(TwinCardNumber.Second))) + val currentTwinCardNumber = TwinsHelper.getTwinCardNumber(card.cardId) + if (TwinsHelper.getTwinCardNumber(firstCardId) == currentTwinCardNumber) { + currentTwinCardNumber?.pairNumber()?.let { + callback(CompletionResult.Failure(WrongTwinCard(it))) + } return } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 7917ddaa07..8310047019 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -8,9 +8,9 @@ import com.tangem.common.CompletionResult import com.tangem.common.KeyPair import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString +import com.tangem.datasource.api.common.MoshiConverter import com.tangem.domain.common.CardDTO import com.tangem.domain.common.ScanResponse -import com.tangem.datasource.api.common.MoshiConverter import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.tap.common.AssetReader import com.tangem.tap.tangemSdkManager @@ -30,7 +30,7 @@ class TwinCardsManager( suspend fun createFirstWallet(message: Message): CompletionResult { val response = tangemSdkManager.runTaskAsync( - runnable = CreateFirstTwinWalletTask(), + runnable = CreateFirstTwinWalletTask(firstCardId), cardId = firstCardId, initialMessage = message, ) @@ -48,6 +48,7 @@ class TwinCardsManager( ): CompletionResult { val task = CreateSecondTwinWalletTask( firstPublicKey = currentCardPublicKey!!, + firstCardId = firstCardId, issuerKeys = issuerKeyPair, preparingMessage = preparingMessage, creatingWalletMessage = creatingWalletMessage, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index 28475b75fa..3773da7127 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -14,10 +14,13 @@ import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricU import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository -import com.tangem.tap.domain.userWalletList.utils.json.* +import com.tangem.tap.domain.userWalletList.utils.json.ByteArrayKeyAdapter +import com.tangem.tap.domain.userWalletList.utils.json.CardBackupStatusAdapter +import com.tangem.tap.domain.userWalletList.utils.json.ExtendedPublicKeysMapAdapter +import com.tangem.tap.domain.userWalletList.utils.json.ScanResponseDerivedKeysMapAdapter +import com.tangem.tap.domain.userWalletList.utils.json.WalletDerivedKeysMapAdapter -const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" -const val USER_WALLETS_BIOMETRIC_KEY_NAME = "user_wallets" +private const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage" fun UserWalletsListManager.Companion.provideBiometricImplementation( context: Context, @@ -43,7 +46,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) val keysRepository = BiometricUserWalletsKeysRepository( - biometricKeyName = USER_WALLETS_BIOMETRIC_KEY_NAME, moshi = moshi, secureStorage = secureStorage, biometricManager = tangemSdkManager.biometricManager, @@ -61,7 +63,6 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( ) return BiometricUserWalletsListManager( - tangemSdkManager = tangemSdkManager, keysRepository = keysRepository, publicInformationRepository = publicInformationRepository, sensitiveInformationRepository = sensitiveInformationRepository, 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 ad4c7597c9..248719dd20 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 @@ -2,9 +2,8 @@ package com.tangem.tap.domain.userWalletList.implementation import com.tangem.common.* import com.tangem.domain.common.util.UserWalletId -import com.tangem.domain.common.util.encryptionKey -import com.tangem.tap.domain.TangemSdkManager 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 @@ -20,7 +19,6 @@ import timber.log.Timber @OptIn(ExperimentalCoroutinesApi::class) internal class BiometricUserWalletsListManager( - private val tangemSdkManager: TangemSdkManager, private val keysRepository: UserWalletsKeysRepository, private val publicInformationRepository: UserWalletsPublicInformationRepository, private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository, @@ -64,10 +62,22 @@ internal class BiometricUserWalletsListManager( override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult { state.update { prevState -> - userWallet.isSaved = false + // If the previous state contains a saved user wallet with the same ID, it is also saved + userWallet.isSaved = prevState.wallets.any { + it.walletId == userWallet.walletId && it.isSaved + } + + val newEncryptionKeys = prevState.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + val newUserWallets = prevState.wallets + .plus(userWallet) + .distinctBy { it.walletId } + prevState.copy( - encryptionKeys = listOf(UserWalletEncryptionKey(userWallet)), - wallets = listOf(userWallet), + encryptionKeys = newEncryptionKeys, + wallets = newUserWallets, + selectedWalletId = userWallet.walletId, ) } @@ -75,15 +85,13 @@ internal class BiometricUserWalletsListManager( .map { state.update { prevState -> prevState.copy( - selectedWalletId = userWallet.walletId, - isLocked = prevState.wallets.size != 1, + isLocked = prevState.wallets.any { it.isLocked }, ) } } } override fun lock() { - tangemSdkManager.biometricManager.unauthenticate() state.update { prevState -> prevState.copy( encryptionKeys = emptyList(), @@ -116,20 +124,23 @@ internal class BiometricUserWalletsListManager( ): CompletionResult = withUnlock { val isWalletSaved = state.value.wallets .filter { it.isSaved } - .flatMap(UserWallet::cardsInWallet) - .contains(userWallet.cardId) + .any { + // Workaround, check [UserWallet.isTwinnedWith] + it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet) + } if (isWalletSaved && !canOverride) { CompletionResult.Failure(UserWalletListError.WalletAlreadySaved) } else { - keysRepository.save( - walletId = userWallet.walletId, - encryptionKey = userWallet.scanResponse.card.encryptionKey, - ) - .doOnSuccess { keys -> + val newEncryptionKeys = state.value.encryptionKeys + .plus(UserWalletEncryptionKey(userWallet)) + .distinctBy { it.walletId } + + keysRepository.store(newEncryptionKeys) + .doOnSuccess { state.update { prevState -> prevState.copy( - encryptionKeys = (keys + prevState.encryptionKeys).distinctBy { it.walletId }, + encryptionKeys = newEncryptionKeys, ) } } @@ -143,28 +154,25 @@ internal class BiometricUserWalletsListManager( } override suspend fun delete(walletIds: List): CompletionResult { - if (state.value.isLocked) { - return CompletionResult.Success(Unit) - } - val walletIdsToRemove = state.value.wallets .map { it.walletId } .filter { it in walletIds } + val remainingEncryptionKeys = state.value.encryptionKeys + .filter { it.walletId !in walletIdsToRemove } changeSelectedWalletIfNeeded(walletIdsToRemove) return sensitiveInformationRepository.delete(walletIdsToRemove) .flatMap { publicInformationRepository.delete(walletIdsToRemove) } - .flatMap { keysRepository.delete(walletIdsToRemove) } - .map { keys -> + .flatMap { keysRepository.store(remainingEncryptionKeys) } + .map { state.update { prevState -> prevState.copy( - encryptionKeys = keys, + encryptionKeys = remainingEncryptionKeys, wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove }, ) } } - .flatMap { loadModels() } } override suspend fun clear(): CompletionResult { @@ -175,12 +183,11 @@ internal class BiometricUserWalletsListManager( .flatMap { keysRepository.clear() } .map { selectedUserWalletRepository.set(null) - tangemSdkManager.biometricManager.unauthenticate() state.update { State() } } } - override suspend fun get(walletId: UserWalletId): CompletionResult = withUnlock { + override suspend fun get(walletId: UserWalletId): CompletionResult { return catching { state.value.wallets.first { it.walletId == walletId } } 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 9fc67b367b..974879ca31 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 @@ -1,12 +1,10 @@ package com.tangem.tap.domain.userWalletList.repository import com.tangem.common.CompletionResult -import com.tangem.domain.common.util.UserWalletId import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey internal interface UserWalletsKeysRepository { suspend fun getAll(): CompletionResult> - suspend fun save(walletId: UserWalletId, encryptionKey: ByteArray): CompletionResult> - suspend fun delete(walletIds: List): CompletionResult> + suspend fun store(encryptionKeys: List): CompletionResult suspend fun clear(): CompletionResult } \ No newline at end of file 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 e1a2ae7329..cd39605d96 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 @@ -6,24 +6,19 @@ import com.squareup.moshi.Types import com.tangem.common.CompletionResult import com.tangem.common.biometric.BiometricManager import com.tangem.common.biometric.BiometricStorage -import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.common.mapFailure import com.tangem.common.services.secure.SecureStorage -import com.tangem.domain.common.util.UserWalletId -import com.tangem.tap.common.extensions.replaceByOrAdd import com.tangem.tap.domain.userWalletList.UserWalletListError import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository internal class BiometricUserWalletsKeysRepository( - biometricKeyName: String, moshi: Moshi, secureStorage: SecureStorage, biometricManager: BiometricManager, ) : UserWalletsKeysRepository { private val biometricStorage = BiometricStorage( - biometricKeyName = biometricKeyName, biometricManager = biometricManager, secureStorage = secureStorage, ) @@ -41,50 +36,16 @@ internal class BiometricUserWalletsKeysRepository( } } - override suspend fun save( - walletId: UserWalletId, - encryptionKey: ByteArray, - ): CompletionResult> { - return getAll() - .flatMap { keys -> - if (keys.any { it.walletId == walletId }) { - return@flatMap CompletionResult.Success(Unit) - } - - val encodedKeys = keys.toMutableList() - .apply { - replaceByOrAdd(UserWalletEncryptionKey(walletId, encryptionKey)) { - it.walletId == walletId - } - } - .encode() - - biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encodedKeys, - ) - } - .flatMap { getAll() } + override suspend fun store(encryptionKeys: List): CompletionResult { + return biometricStorage.store( + key = StorageKey.WalletEncryptionKeys.name, + data = encryptionKeys.encode(), + ) .mapFailure { error -> UserWalletListError.SaveEncryptionKeysError(error.cause ?: error) } } - override suspend fun delete(walletIds: List): CompletionResult> { - return getAll() - .map { keys -> - val keysToRemove = keys.filter { it.walletId in walletIds }.toSet() - (keys - keysToRemove).encode() - } - .flatMap { encodedKeys -> - biometricStorage.store( - key = StorageKey.WalletEncryptionKeys.name, - data = encodedKeys, - ) - } - .flatMap { getAll() } - } - override suspend fun clear(): CompletionResult { return biometricStorage.delete(key = StorageKey.WalletEncryptionKeys.name) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt index 45a7f0e0f9..041ea91557 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletStores/implementation/DefaultWalletStoresManager.kt @@ -165,6 +165,14 @@ internal class DefaultWalletStoresManager( .build(), ) } + .flatMapOnFailure { error -> + when (error) { + is WalletStoresError.WalletManagerNotCreated, + is WalletStoresError.UpdateWalletManagerError, + -> CompletionResult.Success(Unit) + else -> CompletionResult.Failure(error) + } + } } internal data class State( 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 70751b615f..829103e4e7 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 @@ -121,8 +121,12 @@ class DetailsMiddleware { val selectedUserWallet = userWalletsListManager.selectedUserWalletSync if (selectedUserWallet != null) { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedUserWallet) + if (userWalletsListManager.isLockedSync) { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome)) + } else { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(selectedUserWallet) + } } else { userWalletsListManager.lock() store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) @@ -232,6 +236,7 @@ class DetailsMiddleware { } private fun enrollBiometrics() { + Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication) store.dispatchOnMain(NavigationAction.OpenBiometricsSettings) } @@ -268,14 +273,18 @@ class DetailsMiddleware { Timber.e(error, "Wallet saving failed") } .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On)) + preferencesStorage.shouldShowSaveUserWalletScreen = false preferencesStorage.shouldSaveUserWallets = true + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveWallets, enable = true, ), ) + store.onUserWalletSelected(userWallet) } } @@ -284,22 +293,29 @@ class DetailsMiddleware { userWalletsListManager.clear() .flatMap { walletStoresManager.clear() } .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off)) + preferencesStorage.shouldSaveUserWallets = false + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveWallets, enable = false, ), ) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home)) } } private fun saveAccessCodes(state: DetailsState) { + Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) + preferencesStorage.shouldSaveAccessCodes = true tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = state.scanResponse?.card?.isAccessCodeSet == true, ) + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, @@ -311,10 +327,13 @@ class DetailsMiddleware { private suspend fun deleteSavedAccessCodes() { tangemSdkManager.clearSavedUserCodes() .doOnSuccess { + Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off)) + preferencesStorage.shouldSaveAccessCodes = false tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = false, ) + store.dispatchOnMain( DetailsAction.AppSettings.SwitchPrivacySetting.Success( setting = PrivacySetting.SaveAccessCode, 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 8f4282fed9..c879e6ca04 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,9 +93,14 @@ private fun readCard() = scope.launch { userWalletsListManager.save(userWallet) .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) store.onCardScanned(scanResponse) } .doOnSuccess { + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + userWallet.hasAccessCode, + ) store.onUserWalletSelected(userWallet) } .doOnResult { 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 e14d6cff7d..b1ce166222 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 @@ -1,16 +1,20 @@ package com.tangem.tap.features.onboarding +import com.tangem.common.doOnSuccess import com.tangem.domain.common.ProductType import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onCardScanned +import com.tangem.tap.common.extensions.onUserWalletSelected 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.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager +import com.tangem.tap.userWalletsListManager import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -55,6 +59,18 @@ class OnboardingHelper { backupCardsIds: List? = null, ) { when { + // When should save user wallets but manager is locked, then unlock manager with card + preferencesStorage.shouldSaveUserWallets && + userWalletsListManager.isLockedSync -> scope.launch { + val userWallet = UserWalletBuilder(scanResponse).build() + + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) + userWalletsListManager.unlockWithCard(userWallet) + .doOnSuccess { + store.onUserWalletSelected(userWallet) + } + } + // When should save user wallets, then save card without navigate to save wallet screen preferencesStorage.shouldSaveUserWallets -> scope.launch { store.dispatchOnMain( SaveWalletAction.ProvideBackupInfo( @@ -65,6 +81,8 @@ class OnboardingHelper { ) store.dispatchOnMain(SaveWalletAction.Save) } + // When should not save user wallets but device has biometry and save wallet screen has not been shown, + // then open save wallet screen tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch { delay(timeMillis = 1_200) @@ -77,6 +95,7 @@ class OnboardingHelper { ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet)) } + // If device has no biometry and save wallet screen has been shown, then go through old scenario else -> scope.launch { store.onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 761bdd1a53..37e629800a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -234,7 +234,6 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) when (action) { is BackupAction.StartBackup -> { - tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) Analytics.send(Onboarding.Backup.Started()) backupService.discardSavedBackup() val primaryCard = scanResponse?.primaryCard 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 bd8b4d0459..d11d5139fb 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 @@ -4,6 +4,10 @@ import com.tangem.common.CompletionResult import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.common.flatMap +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.AnalyticsParam +import com.tangem.tap.common.analytics.events.MainScreen +import com.tangem.tap.common.analytics.events.Onboarding import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -36,9 +40,9 @@ internal class SaveWalletMiddleware { is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state) is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown() + is SaveWalletAction.Dismiss -> dismiss(state) is SaveWalletAction.Save.Success, is SaveWalletAction.ProvideBackupInfo, - is SaveWalletAction.Dismiss, is SaveWalletAction.CloseError, is SaveWalletAction.Save.Error, is SaveWalletAction.EnrollBiometrics, @@ -74,6 +78,13 @@ internal class SaveWalletMiddleware { ?: store.state.globalState.scanResponse ?: return + if (state.backupInfo != null) { + // TODO: Remove after onboarding refactoring + Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On)) + } else { + Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) + } + scope.launch { val userWallet = UserWalletBuilder(scanResponse) .backupCardsIds(state.backupInfo?.backupCardsIds) @@ -83,6 +94,7 @@ internal class SaveWalletMiddleware { saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet) .flatMap { userWalletsListManager.save(userWallet, canOverride = true) } + .flatMap { userWalletsListManager.selectWallet(userWallet.walletId) } .doOnFailure { error -> store.dispatchOnMain(SaveWalletAction.Save.Error(error)) } @@ -93,21 +105,29 @@ internal class SaveWalletMiddleware { preferencesStorage.shouldSaveAccessCodes = isFirstSavedWallet || preferencesStorage.shouldSaveAccessCodes - val isSavedWalletSelected = - userWalletsListManager.selectedUserWalletSync?.walletId == userWallet.walletId + + tangemSdkManager.setAccessCodeRequestPolicy( + useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && + userWallet.hasAccessCode, + ) store.dispatchOnMain(SaveWalletAction.Save.Success) - if (isSavedWalletSelected) { - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(userWallet) - } else { - store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) - } + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) } } } + private fun dismiss(state: SaveWalletState) { + if (state.backupInfo != null) { + // TODO: Remove after onboarding refactoring + Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.Off)) + } else { + Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off)) + } + } + private fun saveWalletWasShown() { preferencesStorage.shouldShowSaveUserWalletScreen = false } @@ -118,13 +138,10 @@ internal class SaveWalletMiddleware { ): CompletionResult { return when { accessCode != null -> { - tangemSdkManager.unlockBiometricKeys() - .flatMap { - tangemSdkManager.saveAccessCode( - accessCode = accessCode, - cardsIds = cardsInWallet, - ) - } + tangemSdkManager.saveAccessCode( + accessCode = accessCode, + cardsIds = cardsInWallet, + ) } else -> { CompletionResult.Success(Unit) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt index 4ba2a2c082..ea9a272f74 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/AddTokensFragment.kt @@ -5,12 +5,10 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.google.accompanist.appcompattheme.AppCompatTheme @@ -22,6 +20,8 @@ import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.BaseFragment +import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain import com.tangem.tap.features.tokens.redux.TokensAction @@ -40,8 +40,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.rekotlin.StoreSubscriber -class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), - StoreSubscriber { +class AddTokensFragment : BaseFragment(R.layout.fragment_add_tokens), StoreSubscriber { private val binding: FragmentAddTokensBinding by viewBinding(FragmentAddTokensBinding::bind) private var tokensState: MutableState = mutableStateOf(store.state.tokensState) @@ -51,33 +50,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), super.onCreate(savedInstanceState) setHasOptionsMenu(true) Analytics.send(ManageTokens.ScreenOpened()) - - activity?.onBackPressedDispatcher?.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(TokensAction.ResetState) - } - }, - ) - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.slide_right) - exitTransition = inflater.inflateTransition(R.transition.fade) - } - - override fun onStart() { - super.onStart() - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.tokensState == newState.tokensState - }.select { it.tokensState } - } - } - - override fun onStop() { - super.onStop() - store.unsubscribe(this) + addBackPressHandler(this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { @@ -96,11 +69,7 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } val onLoadMore = { - store.dispatch( - TokensAction.LoadMore( - scanResponse = store.state.globalState.scanResponse, - ), - ) + store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse)) } cvCurrencies.setContent { @@ -115,24 +84,23 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), } } - override fun newState(state: TokensState) { - if (activity == null || view == null) return - val toolbarTitle = - if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title - tokensState.value = state - binding.toolbar.title = getString(toolbarTitle) + override fun onStart() { + super.onStart() + store.subscribe(this) { state -> + state + .skipRepeats { oldState, newState -> oldState.tokensState == newState.tokensState } + .select { it.tokensState } + } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.menu_search -> true - R.id.menu_navigate_add_custom_token -> { - Analytics.send(ManageTokens.ButtonCustomToken()) - store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken) - true - } - else -> super.onOptionsItemSelected(item) - } + override fun onStop() { + super.onStop() + store.unsubscribe(this) + } + + override fun onDestroy() { + store.dispatch(TokensAction.ResetState) + super.onDestroy() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -156,25 +124,50 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens), return super.onCreateOptionsMenu(menu, inflater) } - override fun onDestroy() { + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_search -> true + R.id.menu_navigate_add_custom_token -> { + Analytics.send(ManageTokens.ButtonCustomToken()) + store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken) + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun configureTransitions() { + super.configureTransitions() + val inflater = TransitionInflater.from(requireContext()) + enterTransition = inflater.inflateTransition(R.transition.slide_right) + exitTransition = inflater.inflateTransition(R.transition.fade) + } + + override fun handleOnBackPressed() { + super.handleOnBackPressed() + store.dispatch(NavigationAction.PopBackTo()) store.dispatch(TokensAction.ResetState) - super.onDestroy() + } + + override fun newState(state: TokensState) { + if (activity == null || view == null) return + val toolbarTitle = + if (state.allowToAdd) R.string.main_manage_tokens else R.string.search_tokens_title + tokensState.value = state + binding.toolbar.title = getString(toolbarTitle) } } fun SearchView.inputtedTextAsFlow(): Flow = callbackFlow { - val watcher = - setOnQueryTextListener( - object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - return false - } + val watcher = setOnQueryTextListener( + object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean = false - override fun onQueryTextChange(newText: String?): Boolean { - trySend(newText ?: "") - return false - } - }, - ) + override fun onQueryTextChange(newText: String?): Boolean { + trySend(newText ?: "") + return false + } + }, + ) awaitClose { (watcher) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 7cf23fb44c..6d751e8956 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -83,25 +83,21 @@ fun CurrenciesScreen( Scaffold( floatingActionButton = { - if (tokensState.value.allowToAdd) SaveChangesButton(isKeyboardOpen) { - onSaveChanges(addedTokensState.value, addedBlockchainsState.value) + if (tokensState.value.allowToAdd) { + SaveChangesButton(isKeyboardOpen) { + onSaveChanges(addedTokensState.value, addedBlockchainsState.value) + } } }, floatingActionButtonPosition = FabPosition.Center, ) { - AnimatedVisibility( visible = tokensState.value.loadCoinsState == LoadCoinsState.LOADING, enter = fadeIn(), exit = fadeOut(), ) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier.fillMaxSize(), - ) { - CircularProgressIndicator( - color = Color(0xFF1ACE80), - ) + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator(color = Color(0xFF1ACE80)) } } AnimatedVisibility( @@ -148,21 +144,19 @@ private fun toggleBlockchain( currencySymbol = blockchain.currency, ), ) + } else if (isAddedOnMainScreen) { + store.dispatchDialogShow( + WalletDialog.RemoveWalletDialog( + currencyTitle = blockchain.name, + onOk = { + analyticsCurrencyTypeParam.sendOn() + addedBlockchainsState.removeAndNotify(blockchain) + }, + ), + ) } else { - if (isAddedOnMainScreen) { - store.dispatchDialogShow( - WalletDialog.RemoveWalletDialog( - currencyTitle = blockchain.name, - onOk = { - analyticsCurrencyTypeParam.sendOn() - addedBlockchainsState.removeAndNotify(blockchain) - }, - ), - ) - } else { - analyticsCurrencyTypeParam.sendOff() - addedBlockchainsState.removeAndNotify(blockchain) - } + analyticsCurrencyTypeParam.sendOff() + addedBlockchainsState.removeAndNotify(blockchain) } } else { analyticsCurrencyTypeParam.sendOn() diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt index cded760ba9..c4b1b21abe 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/currencyItem/CurrencyExpandedContent.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.domain.tokens.Contract import com.tangem.tap.domain.tokens.Currency import com.tangem.tap.features.tokens.redux.ContractAddress import com.tangem.tap.features.tokens.redux.TokenWithBlockchain @@ -30,22 +31,20 @@ fun CurrencyExpandedContent( exit = fadeOut() + shrinkVertically(), ) { val blockchains = currency.contracts.map { it.blockchain } - Column( - modifier = Modifier - .fillMaxWidth(), - ) { + Column(modifier = Modifier.fillMaxWidth()) { blockchains.mapIndexed { index, blockchain -> - val contract = currency.contracts.firstOrNull { it.blockchain == blockchain } + val currencyContract = currency.contracts + .firstOrNull { it.blockchain == blockchain } ?: return@mapIndexed - val added = if (contract.address != null) { - addedTokens.map { it.token.contractAddress }.contains(contract.address) + val added = if (currencyContract.address != null) { + addedTokens.any(currencyContract::isInclude) } else { addedBlockchains.contains(blockchain) } NetworkItem( currency = currency, - contract = contract, + contract = currencyContract, blockchain = blockchain, allowToAdd = allowToAdd, added = added, @@ -57,4 +56,7 @@ fun CurrencyExpandedContent( } } } -} \ No newline at end of file +} + +private fun Contract.isInclude(addedToken: TokenWithBlockchain): Boolean = + address == addedToken.token.contractAddress && blockchain == addedToken.blockchain \ No newline at end of file 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 28f6fcc965..bbe6f90378 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 @@ -8,6 +8,7 @@ import com.tangem.common.extensions.guard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.events.AnalyticsParam +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 @@ -181,6 +182,7 @@ class MultiWalletMiddleware { selectedWallet: UserWallet, state: WalletState?, ) = scope.launch { + Analytics.send(MainScreen.CardWasScanned()) ScanCardProcessor.scan( cardId = selectedWallet.cardId, additionalBlockchainsToDerive = state?.missingDerivations?.map { it.blockchain }, 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 83cca6c428..f6e0c47011 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 @@ -12,6 +12,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.operations.attestation.Attestation import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MainScreen import com.tangem.tap.common.analytics.events.Token import com.tangem.tap.common.extensions.copyToClipboard import com.tangem.tap.common.extensions.dispatchDebugErrorNotification @@ -349,9 +350,11 @@ class WalletMiddleware { private fun changeWallet() { when { userWalletsListManager.hasSavedUserWallets -> { + Analytics.send(MainScreen.ButtonMyWallets()) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletSelector)) } else -> { + Analytics.send(MainScreen.ButtonScanCard()) store.dispatch(WalletAction.Scan) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt index 2569627f80..1ceda1b7b4 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/MultiWalletView.kt @@ -51,7 +51,6 @@ class MultiWalletView : WalletView() { btnAddToken.show() } - override fun onViewCreated() { setupWalletsRecyclerView() } @@ -69,7 +68,7 @@ class MultiWalletView : WalletView() { val fragment = fragment ?: return val binding = binding ?: return - handleTotalBalance(binding, state.totalBalance, state.state) + handleTotalBalance(binding, state.totalBalance, state.state, state.walletsData.size) handleBackupWarning(binding, state.showBackupWarning) handleRescanWarning(binding, state.missingDerivations.isNotEmpty()) setupWalletCardNumber(binding, state.walletCardsCount) @@ -137,29 +136,34 @@ class MultiWalletView : WalletView() { binding: FragmentWalletBinding, totalBalance: TotalBalance?, progressState: ProgressState, + walletsCount: Int, ) = with(binding.lCardTotalBalance) { - if (totalBalance == null) { - veilBalance.animateVisibility(show = true) - root.isVisible = progressState == ProgressState.Loading + if (walletsCount == 0) { + root.isVisible = false } else { - root.isVisible = true + if (totalBalance == null) { + veilBalance.animateVisibility(show = true) + root.isVisible = progressState == ProgressState.Loading + } else { + root.isVisible = true - // Skip changes when on refreshing state - if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { - return@with - } + // Skip changes when on refreshing state + if (totalBalance.state == ProgressState.Refreshing || progressState == ProgressState.Refreshing) { + return@with + } - veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) - tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) - tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) + veilBalance.animateVisibility(show = totalBalance.state == ProgressState.Loading) + tvBalance.animateVisibility(show = totalBalance.state != ProgressState.Loading) + tvProcessing.animateVisibility(show = totalBalance.state == ProgressState.Error) - tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( - currencySymbol = totalBalance.fiatCurrency.symbol, - ) - tvCurrencyName.text = totalBalance.fiatCurrency.code + tvBalance.text = totalBalance.fiatAmount.formatAmountAsSpannedString( + currencySymbol = totalBalance.fiatCurrency.symbol, + ) + tvCurrencyName.text = totalBalance.fiatCurrency.code - tvCurrencyName.setOnClickListener { - store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) + tvCurrencyName.setOnClickListener { + store.dispatch(WalletAction.AppCurrencyAction.ChooseAppCurrency) + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt index 3c61afc62c..1417914782 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/UserWalletModel.kt @@ -8,15 +8,16 @@ data class UserWalletModel( val artworkUrl: String, val type: Type, val fiatBalance: TotalFiatBalance, + val isLocked: Boolean, ) { sealed interface Type { data class SingleCurrency( - val blockchainName: String? = null, + val blockchainName: String, ) : Type data class MultiCurrency( val cardsInWallet: Int, - val tokensCount: Int = 0, + val tokensCount: Int, ) : Type } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt index d18cf44dec..957661126f 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt @@ -37,6 +37,10 @@ internal sealed interface WalletSelectorAction : Action { val walletId: String, ) : WalletSelectorAction + data class UnlockWalletWithCard( + val walletId: String, + ) : WalletSelectorAction + data class RenameWallet( val walletId: String, val newName: String, @@ -56,5 +60,6 @@ internal sealed interface WalletSelectorAction : Action { ) : WalletSelectorAction data class HandleError(val error: TangemError) : WalletSelectorAction + object CloseError : WalletSelectorAction } \ 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 ae053d797b..dd4c4ff88b 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 @@ -7,6 +7,8 @@ import com.tangem.common.flatMap import com.tangem.common.map import com.tangem.domain.common.ScanResponse import com.tangem.domain.common.util.UserWalletId +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState @@ -24,7 +26,6 @@ import com.tangem.tap.tangemSdkManager import com.tangem.tap.totalFiatBalanceCalculator import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletStoresManager -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -60,6 +61,9 @@ internal class WalletSelectorMiddleware { is WalletSelectorAction.SelectWallet -> { selectWallet(action.walletId) } + is WalletSelectorAction.UnlockWalletWithCard -> { + unlockWalletWithCard(action.walletId) + } is WalletSelectorAction.RemoveWallets -> { removeWallets(action.walletIdsToRemove, state) } @@ -105,6 +109,8 @@ internal class WalletSelectorMiddleware { } private fun unlockWalletsWithBiometry() { + Analytics.send(MyWallets.Button.UnlockWithBiometrics) + scope.launch { userWalletsListManager.unlockWithBiometry() .doOnFailure { error -> @@ -117,6 +123,8 @@ internal class WalletSelectorMiddleware { } private fun addWallet() = scope.launch { + Analytics.send(MyWallets.Button.ScanNewCard) + scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() @@ -125,15 +133,13 @@ internal class WalletSelectorMiddleware { store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) } .doOnSuccess { - val selectedWallet = userWalletsListManager.selectedUserWallet.first() - val isSavedWalletSelected = userWallet == selectedWallet - store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + Analytics.send(MyWallets.CardWasScanned) - if (isSavedWalletSelected) { - updateAccessCodeRequestPolicy(selectedWallet) - store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) - store.onUserWalletSelected(selectedWallet) - } + userWalletsListManager.selectWallet(userWallet.walletId) + store.dispatchOnMain(WalletSelectorAction.AddWallet.Success) + updateAccessCodeRequestPolicy(userWallet) + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) } } } @@ -152,7 +158,43 @@ internal class WalletSelectorMiddleware { } } + private fun unlockWalletWithCard(id: String) { + scope.launch { + userWalletsListManager.get(UserWalletId(id)) + .flatMap { userWallet -> + updateUserWalletWithScannedCard(userWallet) + } + .flatMap { updatedUserWallet -> + unlockUserWallet(updatedUserWallet) + } + .doOnFailure { error -> + store.dispatchOnMain(WalletSelectorAction.HandleError(error)) + } + } + } + + private suspend fun updateUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { + return tangemSdkManager.scanCard(userWallet.cardId) + .map { scannedCard -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + card = scannedCard, + ), + ) + } + } + + private suspend fun unlockUserWallet(userWallet: UserWallet): CompletionResult { + return userWalletsListManager.unlockWithCard(userWallet) + .doOnSuccess { + store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet)) + store.onUserWalletSelected(userWallet) + } + } + private fun removeWallets(walletIdsToRemove: List, state: WalletSelectorState) { + Analytics.send(MyWallets.Button.DeleteWalletTapped) + scope.launch { when (walletIdsToRemove.size) { state.wallets.size -> clearUserWallets() @@ -165,6 +207,8 @@ internal class WalletSelectorMiddleware { } private fun renameWallet(walletId: String, newName: String) { + Analytics.send(MyWallets.Button.EditWalletTapped) + scope.launch { userWalletsListManager.get(walletId = UserWalletId(walletId)) .map { it.copy(name = newName) } @@ -221,7 +265,7 @@ internal class WalletSelectorMiddleware { private fun updateAccessCodeRequestPolicy(userWallet: UserWallet) { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - userWallet.scanResponse.card.isAccessCodeSet, + userWallet.hasAccessCode, ) } @@ -233,13 +277,7 @@ internal class WalletSelectorMiddleware { is UserWalletModel.Type.MultiCurrency -> type.copy( tokensCount = walletStores.flatMap { it.walletsData }.size, ) - is UserWalletModel.Type.SingleCurrency -> type.copy( - blockchainName = walletStores - .firstOrNull() - ?.blockchainNetwork - ?.blockchain - ?.fullName, - ) + is UserWalletModel.Type.SingleCurrency -> type }, fiatBalance = totalFiatBalanceCalculator.calculate( prevAmount = fiatBalance.amount, diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt index b9f2ca6c70..959c45c8d9 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt @@ -56,6 +56,7 @@ internal object WalletSelectorReducer { ) is WalletSelectorAction.WalletStoresChanged, is WalletSelectorAction.SelectWallet, + is WalletSelectorAction.UnlockWalletWithCard, is WalletSelectorAction.RemoveWallets, is WalletSelectorAction.RenameWallet, -> state @@ -66,10 +67,14 @@ internal object WalletSelectorReducer { return this.map { userWallet -> prevWallets .find { it.id == userWallet.walletId.stringValue } - ?.copy( - name = userWallet.name, - artworkUrl = userWallet.artworkUrl, - ) + ?.let { + it.copy( + name = userWallet.name, + artworkUrl = userWallet.artworkUrl, + isLocked = userWallet.isLocked, + type = userWallet.getType(prevType = it.type), + ) + } ?: with(userWallet) { UserWalletModel( id = walletId.stringValue, @@ -77,6 +82,7 @@ internal object WalletSelectorReducer { artworkUrl = artworkUrl, type = getType(), fiatBalance = TotalFiatBalance.Loading, + isLocked = isLocked, ) } } @@ -90,15 +96,18 @@ internal object WalletSelectorReducer { } } - private fun UserWallet.getType(): UserWalletModel.Type { + private fun UserWallet.getType(prevType: UserWalletModel.Type? = null): UserWalletModel.Type { return if (scanResponse.card.isMultiwalletAllowed) { UserWalletModel.Type.MultiCurrency( cardsInWallet = (scanResponse.card.backupStatus as? CardDTO.BackupStatus.Active) ?.cardCount?.inc() ?: 1, + tokensCount = (prevType as? UserWalletModel.Type.MultiCurrency)?.tokensCount ?: 0, ) } else { - UserWalletModel.Type.SingleCurrency() + UserWalletModel.Type.SingleCurrency( + blockchainName = scanResponse.getBlockchain().fullName, + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt index 34ce8d3ef9..6694b8b663 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/Mapper.kt @@ -60,6 +60,7 @@ private fun List.toUiModels( name = name, imageUrl = artworkUrl, balance = balance, + isLocked = isLocked, cardsInWallet = type.cardsInWallet, tokensCount = type.tokensCount, ) @@ -68,6 +69,7 @@ private fun List.toUiModels( name = name, imageUrl = artworkUrl, balance = balance, + isLocked = isLocked, tokenName = type.blockchainName ?: "—", ) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 50ee972e7d..717f82ed16 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -1,5 +1,7 @@ package com.tangem.tap.features.walletSelector.ui +import android.app.Dialog +import android.os.Bundle import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,6 +16,8 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.fragment.app.viewModels import com.tangem.core.ui.fragments.ComposeBottomSheetFragment import com.tangem.core.ui.res.TangemTheme +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.events.MyWallets import com.tangem.tap.features.details.ui.cardsettings.resolveReference import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent @@ -22,6 +26,12 @@ import com.tangem.tap.features.walletSelector.ui.model.RenameWalletDialog internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment() { private val viewModel by viewModels() + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + Analytics.send(MyWallets.MyWalletsScreenOpened) + + return super.onCreateDialog(savedInstanceState) + } + @Composable override fun provideState(): State { return viewModel.state.collectAsState() 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 ab2608b497..a7eaa3c4f4 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 @@ -37,7 +37,9 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber Unit + isLocked -> { + store.dispatch(WalletSelectorAction.UnlockWalletWithCard(walletId)) + } editingWalletsIds.isNotEmpty() && !editingWalletsIds.contains(walletId) -> { editWallet(walletId) } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt index 383ed51f5d..2fab91adc1 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/MockData.kt @@ -14,6 +14,7 @@ internal object MockData { ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", + isLocked = false, tokensCount = 12, cardsInWallet = 3, ) @@ -26,6 +27,7 @@ internal object MockData { ), name = "Wallet", imageUrl = "https://app.tangem.com/cards/card_default.png", + isLocked = false, tokenName = "Ethereum", ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt index 5e972b5dfc..6c57a43f00 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WallectSelectorScreenContent.kt @@ -72,7 +72,6 @@ internal fun WalletSelectorScreenContent( singleCurrencyWallets = state.singleCurrencyWallets, selectedWalletId = state.selectedWalletId, checkedWalletIds = state.editingWalletsIds, - isLocked = state.isLocked, onWalletClick = onWalletClick, onWalletLongClick = onWalletLongClick, ) @@ -141,7 +140,6 @@ private fun WalletsList( singleCurrencyWallets: List, selectedWalletId: String?, checkedWalletIds: List, - isLocked: Boolean, onWalletClick: (walletId: String) -> Unit, onWalletLongClick: (walletId: String) -> Unit, ) { @@ -166,7 +164,6 @@ private fun WalletsList( wallet = wallet, isSelected = wallet.id == selectedWalletId, isChecked = wallet.id in checkedWalletIds, - isLocked = isLocked && wallet.id != selectedWalletId, ) } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt index ada0168d08..8ad4d7a2fc 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/WalletItem.kt @@ -39,7 +39,6 @@ internal fun WalletItem( wallet: UserWalletItem, isSelected: Boolean, isChecked: Boolean, - isLocked: Boolean, ) { Row( modifier = modifier, @@ -59,7 +58,7 @@ internal fun WalletItem( SpacerW6() TokensInfo( modifier = Modifier.weight(weight = .4f), - isLocked = isLocked, + isLocked = wallet.isLocked, balance = wallet.balance, tokensCount = (wallet as? MultiCurrencyUserWalletItem)?.tokensCount, ) diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt index cadbde80db..c546cd131e 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/UserWalletItem.kt @@ -1,15 +1,14 @@ package com.tangem.tap.features.walletSelector.ui.model -import androidx.compose.runtime.Immutable import com.tangem.tap.features.details.ui.cardsettings.TextReference import com.tangem.wallet.R -@Immutable internal sealed interface UserWalletItem { val id: String val name: String val imageUrl: String val balance: Balance + val isLocked: Boolean val headerText: TextReference get() = when (this) { @@ -17,7 +16,6 @@ internal sealed interface UserWalletItem { is SingleCurrencyUserWalletItem -> TextReference.Res(R.string.user_wallet_list_single_header) } - @Immutable data class Balance( val amount: String, val isLoading: Boolean, @@ -29,6 +27,7 @@ internal data class MultiCurrencyUserWalletItem( override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, + override val isLocked: Boolean, val tokensCount: Int, val cardsInWallet: Int, ) : UserWalletItem @@ -38,5 +37,6 @@ internal data class SingleCurrencyUserWalletItem( override val name: String, override val imageUrl: String, override val balance: UserWalletItem.Balance, + override val isLocked: Boolean, val tokenName: String, ) : UserWalletItem \ No newline at end of file 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 88c0038a3c..fb5b8a626d 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 @@ -61,7 +61,7 @@ internal class WelcomeMiddleware { if (selectedUserWallet != null) { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - selectedUserWallet.scanResponse.card.isAccessCodeSet, + selectedUserWallet.hasAccessCode, ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) @@ -78,14 +78,12 @@ internal class WelcomeMiddleware { scanCardInternal { scanResponse -> val userWallet = UserWalletBuilder(scanResponse).build() + tangemSdkManager.setAccessCodeRequestPolicy(useBiometricsForAccessCode = false) userWalletsListManager.unlockWithCard(userWallet) .doOnFailure { error -> store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error)) } .doOnSuccess { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = false, - ) store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet)) store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) store.onUserWalletSelected(userWallet) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index e43bcc7e0d..e5985c63df 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -59,7 +59,8 @@ object Versions { // region Tangem const val tangemBlockchainSdk = "develop-141" - const val tangemCardSgk = "develop-171" + + const val tangemCardSgk = "develop-173" // endregion Tangem // region Testing diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index e518041674..edfab6f5e2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -14,7 +14,7 @@ interface TangemTechApi { @GET("coins") suspend fun coins( @Query("contractAddress") contractAddress: String? = null, - @Query("exchangeable") exchangeable: Boolean? = false, + @Query("exchangeable") exchangeable: Boolean? = null, @Query("networkIds") networkIds: String? = null, @Query("active") active: Boolean? = null, @Query("searchText") searchText: String? = null,