diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 16456eb969..d449675c38 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -166,10 +166,34 @@ internal object WalletsDomainModule { @Provides @Singleton - fun providesUnlockWalletUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase { + fun providesUnlockWalletsUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase { return UnlockWalletsUseCase(userWalletsListManager = userWalletsListManager) } + @Provides + @Singleton + fun providesUnlockWalletUseCase( + nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase, + userWalletsListRepository: UserWalletsListRepository, + ): UnlockWalletUseCase { + return UnlockWalletUseCase( + nonBiometricUnlockWalletUseCase = nonBiometricUnlockWalletUseCase, + userWalletsListRepository = userWalletsListRepository, + ) + } + + @Provides + @Singleton + fun providesNonBiometricUnlockWalletUseCase( + userWalletsListRepository: UserWalletsListRepository, + walletsRepository: WalletsRepository, + ): NonBiometricUnlockWalletUseCase { + return NonBiometricUnlockWalletUseCase( + userWalletsListRepository = userWalletsListRepository, + walletsRepository = walletsRepository, + ) + } + @Provides @Singleton fun providesSelectWalletUseCase( diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt index bc1e70f2a5..b8f7104b03 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt @@ -32,6 +32,7 @@ class UserWalletItemUMConverter( private val balance: TotalFiatBalance? = null, private val isBalanceHidden: Boolean = false, private val isAuthMode: Boolean = false, + private val isClickableIfLocked: Boolean = false, private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None, artwork: UserWalletItemUM.ImageState? = null, ) : Converter { @@ -54,7 +55,7 @@ class UserWalletItemUMConverter( } private fun isEnabled(userWallet: UserWallet): Boolean { - return isAuthMode || userWallet.isLocked.not() + return isAuthMode || isClickableIfLocked || userWallet.isLocked.not() } private fun getInfo(userWallet: UserWallet): UserWalletItemUM.Information.Loaded { diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/NonBiometricUnlockWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/NonBiometricUnlockWalletUseCase.kt new file mode 100644 index 0000000000..c3b777d3af --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/NonBiometricUnlockWalletUseCase.kt @@ -0,0 +1,43 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.error.UnlockWalletError +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.models.wallet.isLocked +import com.tangem.domain.wallets.repository.WalletsRepository + +class NonBiometricUnlockWalletUseCase( + private val userWalletsListRepository: UserWalletsListRepository, + private val walletsRepository: WalletsRepository, +) { + + suspend operator fun invoke(userWalletId: UserWalletId): Either = either { + val userWallet = userWalletsListRepository.userWalletsSync() + .find { it.walletId == userWalletId } + ?: raise(UnlockWalletError.UserWalletNotFound) + + if (!userWallet.isLocked) { + return@either + } + + val method = when (userWallet) { + is UserWallet.Cold -> UserWalletsListRepository.UnlockMethod.Scan() + is UserWallet.Hot -> UserWalletsListRepository.UnlockMethod.AccessCode + } + + userWalletsListRepository.unlock(userWalletId, method) + .onRight { + if (walletsRepository.useBiometricAuthentication()) { + // After successful unlock, set biometric lock if applicable + userWalletsListRepository.setLock( + userWalletId, + UserWalletsListRepository.LockMethod.Biometric, + ) + } + } + .bind() + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletUseCase.kt new file mode 100644 index 0000000000..9d24f06f3c --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletUseCase.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.error.UnlockWalletError +import com.tangem.domain.models.wallet.UserWalletId + +class UnlockWalletUseCase( + private val userWalletsListRepository: UserWalletsListRepository, + private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase, +) { + + suspend operator fun invoke(userWalletId: UserWalletId): Either = either { + userWalletsListRepository.unlockAllWallets() + .mapLeft { nonBiometricUnlockWalletUseCase(userWalletId).bind() } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt index 4d46212807..3bbed19358 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockWalletsUseCase.kt @@ -17,6 +17,7 @@ import com.tangem.domain.wallets.models.UnlockWalletsError * [REDACTED_AUTHOR] */ +@Deprecated("Use NonBiometricUnlockWalletUseCase after migrating to new wallets repository") class UnlockWalletsUseCase(private val userWalletsListManager: UserWalletsListManager) { suspend operator fun invoke(type: UnlockType = UnlockType.ANY): Either = either { diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt index a096f5231d..511bcb27d8 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt @@ -8,7 +8,9 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase +import com.tangem.domain.wallets.usecase.UnlockWalletUseCase import com.tangem.features.details.entity.UserWalletListUM import com.tangem.features.details.impl.R import com.tangem.features.details.utils.UserWalletSaver @@ -21,6 +23,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -33,6 +37,7 @@ internal class UserWalletListModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val userWalletSaver: UserWalletSaver, private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val unlockWalletUseCase: UnlockWalletUseCase, ) : Model() { private val isWalletSavingInProgress: MutableStateFlow = MutableStateFlow(value = false) @@ -40,7 +45,8 @@ internal class UserWalletListModel @Inject constructor( messageSender = messageSender, onlyMultiCurrency = false, isAuthMode = false, - onWalletClick = { userWalletId -> router.push(AppRoute.WalletSettings(userWalletId)) }, + isClickableIfLocked = hotWalletFeatureToggles.isHotWalletEnabled, + onWalletClick = ::onWalletClicked, ) val state: MutableStateFlow = MutableStateFlow( @@ -87,4 +93,20 @@ internal class UserWalletListModel @Inject constructor( } } } + + private fun onWalletClicked(userWalletId: UserWalletId) { + if (hotWalletFeatureToggles.isHotWalletEnabled) { + modelScope.launch { + unlockWalletUseCase(userWalletId) + .onRight { + router.push(AppRoute.WalletSettings(userWalletId)) + } + .onLeft { error -> + Timber.e("Failed to unlock wallet $userWalletId: $error") + } + } + } else { + router.push(AppRoute.WalletSettings(userWalletId)) + } + } } \ No newline at end of file diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletsFetcher.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletsFetcher.kt index 97868f231b..1705d2e3ad 100644 --- a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletsFetcher.kt +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/utils/UserWalletsFetcher.kt @@ -15,6 +15,7 @@ interface UserWalletsFetcher { messageSender: UiMessageSender, onlyMultiCurrency: Boolean, isAuthMode: Boolean, + isClickableIfLocked: Boolean, onWalletClick: (UserWalletId) -> Unit, ): UserWalletsFetcher } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 340c7c3d6d..8c868fcabf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -14,7 +14,6 @@ import com.tangem.core.ui.components.bottomsheets.message.* import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage -import com.tangem.core.ui.message.DialogMessage.Companion.invoke import com.tangem.core.ui.message.SnackbarMessage import com.tangem.core.ui.message.bottomSheetMessage import com.tangem.domain.card.SetCardWasScannedUseCase @@ -125,6 +124,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val getUserWalletUseCase: GetUserWalletUseCase, private val scanCardToUnlockWalletClickHandler: ScanCardToUnlockWalletClickHandler, private val unlockWalletsUseCase: UnlockWalletsUseCase, + private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase, @@ -200,14 +200,8 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( modelScope.launch { userWalletsListRepository.unlockAllWallets() .onLeft { - val selectedUserWallet = getSelectedUserWallet() ?: return@onLeft - val selectedUserWalletId = selectedUserWallet.walletId - val method = when (selectedUserWallet) { - is UserWallet.Cold -> UserWalletsListRepository.UnlockMethod.Scan() - is UserWallet.Hot -> UserWalletsListRepository.UnlockMethod.AccessCode - } - userWalletsListRepository - .unlock(stateHolder.getSelectedWalletId(), method) + val selectedUserWalletId = stateHolder.getSelectedWalletId() + nonBiometricUnlockWalletUseCase(selectedUserWalletId) .onLeft { when (it) { UnlockWalletError.AlreadyUnlocked -> Unit diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletsFetcher.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletsFetcher.kt index f595824173..b163d627d6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletsFetcher.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/utils/DefaultUserWalletsFetcher.kt @@ -48,6 +48,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor( @Assisted private val messageSender: UiMessageSender, @Assisted("onlyMultiCurrency") private val onlyMultiCurrency: Boolean, @Assisted("isAuthMode") private val isAuthMode: Boolean, + @Assisted("isClickableIfLocked") private val isClickableIfLocked: Boolean, private val userWalletImageFetcher: UserWalletImageFetcher, dispatchers: CoroutineDispatcherProvider, ) : UserWalletsFetcher { @@ -60,6 +61,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor( val uiModels = UserWalletItemUMConverter( onClick = onWalletClick, isAuthMode = isAuthMode, + isClickableIfLocked = isClickableIfLocked, ).convertList(wallets) .toImmutableList() @@ -144,6 +146,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor( UserWalletItemUM.EndIcon.None }, isAuthMode = isAuthMode, + isClickableIfLocked = isClickableIfLocked, ) .convert(userWallet) } @@ -163,6 +166,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor( messageSender: UiMessageSender, @Assisted("onlyMultiCurrency") onlyMultiCurrency: Boolean, @Assisted("isAuthMode") isAuthMode: Boolean, + @Assisted("isClickableIfLocked") isClickableIfLocked: Boolean, onWalletClick: (UserWalletId) -> Unit, ): DefaultUserWalletsFetcher } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcUserWalletsFetcher.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcUserWalletsFetcher.kt index 584b345971..742a0205bd 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcUserWalletsFetcher.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/utils/WcUserWalletsFetcher.kt @@ -30,6 +30,7 @@ internal class WcUserWalletsFetcher( messageSender = messageSender, onlyMultiCurrency = true, isAuthMode = false, + isClickableIfLocked = false, onWalletClick = { onWalletSelected(it) }, ) diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 3b301a83a1..926b325964 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -17,6 +17,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.domain.wallets.usecase.NonBiometricUnlockWalletUseCase import com.tangem.features.wallet.utils.UserWalletsFetcher import com.tangem.features.welcome.impl.R import com.tangem.features.welcome.impl.ui.state.WelcomeUM @@ -38,6 +39,7 @@ internal class WelcomeModel @Inject constructor( private val router: Router, private val uiMessageSender: UiMessageSender, private val userWalletsListRepository: UserWalletsListRepository, + private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, private val walletsRepository: WalletsRepository, userWalletsFetcherFactory: UserWalletsFetcher.Factory, @@ -50,6 +52,7 @@ internal class WelcomeModel @Inject constructor( messageSender = uiMessageSender, onlyMultiCurrency = false, isAuthMode = true, + isClickableIfLocked = true, onWalletClick = { walletId -> modelScope.launch { val userWallets = userWalletsListRepository.userWalletsSync() @@ -109,7 +112,7 @@ internal class WelcomeModel @Inject constructor( val hotWalletLockedWithAccessCode = userWalletsListRepository.userWalletsSync() .first { it.isLocked && it is UserWallet.Hot } as UserWallet.Hot uiState.value = WelcomeUM.Empty - unlockWallet(hotWalletLockedWithAccessCode.walletId, UserWalletsListRepository.UnlockMethod.AccessCode) + nonBiometricUnlockWallet(hotWalletLockedWithAccessCode.walletId) } } @@ -161,15 +164,11 @@ internal class WelcomeModel @Inject constructor( return@launch } - val unlockMethod = when (userWallet) { - is UserWallet.Cold -> UserWalletsListRepository.UnlockMethod.Scan() - is UserWallet.Hot -> { - uiState.value = WelcomeUM.Empty - UserWalletsListRepository.UnlockMethod.AccessCode - } + if (userWallet is UserWallet.Hot) { + uiState.value = WelcomeUM.Empty } - unlockWallet(userWallet.walletId, unlockMethod) + nonBiometricUnlockWallet(userWallet.walletId) setSelectWalletState() } @@ -177,8 +176,8 @@ internal class WelcomeModel @Inject constructor( return canUseBiometryUseCase() && walletsRepository.useBiometricAuthentication() } - suspend fun unlockWallet(userWalletId: UserWalletId, unlockMethod: UserWalletsListRepository.UnlockMethod) { - userWalletsListRepository.unlock(userWalletId, unlockMethod) + suspend fun nonBiometricUnlockWallet(userWalletId: UserWalletId) { + nonBiometricUnlockWalletUseCase(userWalletId) .onRight { routedOut = true userWalletsListRepository.select(userWalletId)