Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-20 21:14:45 +07:00
parent c6578fcad5
commit 6e642be3ca
19 changed files with 478 additions and 50 deletions

View file

@ -36,6 +36,8 @@ import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.managetokens.navigation.ManageTokensUi
import com.tangem.features.send.api.navigation.SendRouter
@ -140,6 +142,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var deepLinksRegistry: DeepLinksRegistry
@Inject
lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles
@Inject
lateinit var generalUserWalletsListManager: UserWalletsListManager
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>
@ -438,7 +446,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
val canSaveWallets = userWalletsListManager is BiometricUserWalletsListManager
val canSaveWallets = if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
} else {
userWalletsListManager is BiometricUserWalletsListManager
}
val hasSavedWallets = userWalletsListManager.hasUserWallets
if (canSaveWallets && hasSavedWallets) {

View file

@ -35,6 +35,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -155,6 +156,12 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var accountCreator: AccountCreator
@Inject
lateinit var userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles
@Inject
lateinit var generalUserWalletsListManager: UserWalletsListManager
// endregion Injected
override fun onCreate() {
@ -183,8 +190,13 @@ internal class TapApplication : Application(), ImageLoaderFactory {
// TODO: Try to performance and user experience.
// [REDACTED_JIRA]
runBlocking {
initUserWalletsListManager()
featureTogglesManager.init()
if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
store.dispatch(GlobalAction.UpdateUserWalletsListManager(generalUserWalletsListManager))
} else {
initUserWalletsListManager()
}
}
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
@ -232,6 +244,8 @@ internal class TapApplication : Application(), ImageLoaderFactory {
sendFeatureToggles = sendFeatureToggles,
blockchainDataStorage = blockchainDataStorage,
accountCreator = accountCreator,
userWalletsListManagerFeatureToggles = userWalletsListManagerFeatureToggles,
generalUserWalletsListManager = generalUserWalletsListManager,
),
),
)

View file

@ -7,6 +7,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import kotlinx.coroutines.Dispatchers
@ -87,4 +88,10 @@ fun Store<*>.dispatchOpenUrl(url: String) {
fun Store<*>.dispatchShare(url: String) {
dispatch(NavigationAction.Share(url))
}
inline fun <reified T> Store<AppState>.inject(getDependency: DaggerGraphState.() -> T?): T {
return requireNotNull(state.daggerGraphState.getDependency()) {
"${T::class.simpleName} isn't initialized "
}
}

View file

@ -3,11 +3,13 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.preferencesStorage
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action
@ -95,10 +97,17 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
)
}
is GlobalAction.UpdateUserWalletsListManager -> {
appStateHolder.userWalletsListManager = action.manager
globalState.copy(
userWalletsListManager = action.manager,
)
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (featureToggles.isGeneralManagerEnabled) {
val generalUserWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
appStateHolder.userWalletsListManager = generalUserWalletsListManager
globalState.copy(userWalletsListManager = generalUserWalletsListManager)
} else {
appStateHolder.userWalletsListManager = action.manager
globalState.copy(userWalletsListManager = action.manager)
}
}
is GlobalAction.ChangeAppThemeMode -> globalState.copy(
appThemeMode = action.appThemeMode,

View file

@ -0,0 +1,12 @@
package com.tangem.tap.domain.userWalletList
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
internal class DefaultUserWalletsListManagerFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : UserWalletsListManagerFeatureToggles {
override val isGeneralManagerEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED")
}

View file

@ -0,0 +1,23 @@
package com.tangem.tap.domain.userWalletList.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.tap.domain.userWalletList.DefaultUserWalletsListManagerFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object UserWalletsListManagerFeatureTogglesModule {
@Provides
@Singleton
fun provideUserWalletsListManagerFeatureToggles(
featureTogglesManager: FeatureTogglesManager,
): UserWalletsListManagerFeatureToggles {
return DefaultUserWalletsListManagerFeatureToggles(featureTogglesManager)
}
}

View file

@ -0,0 +1,108 @@
package com.tangem.tap.domain.userWalletList.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
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.tangemSdkManager
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object UserWalletsListManagerModule {
@Provides
@Singleton
fun provideGeneralUserWalletsListManager(
@ApplicationContext applicationContext: Context,
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): UserWalletsListManager {
return GeneralUserWalletsListManager(
runtimeUserWalletsListManager = RuntimeUserWalletsListManager(),
biometricUserWalletsListManager = createBiometricUserWalletsListManager(applicationContext),
appPreferencesStore = appPreferencesStore,
dispatchers = dispatchers,
)
}
private fun createBiometricUserWalletsListManager(applicationContext: Context): UserWalletsListManager {
val moshi = Moshi.Builder()
.add(WalletDerivedKeysMapAdapter())
.add(ScanResponseDerivedKeysMapAdapter())
.add(ByteArrayKeyAdapter())
.add(ExtendedPublicKeysMapAdapter())
.add(CardBackupStatusAdapter())
.add(DerivationPathAdapterWithMigration())
.add(TangemSdkAdapter.DateAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
.add(KotlinJsonAdapterFactory())
.build()
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
),
)
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
),
keystoreManager = DelegatedKeystoreManager(
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
),
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
moshi = moshi,
secureStorage = secureStorage,
)
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(secureStorage = secureStorage)
return BiometricUserWalletsListManager(
keysRepository = keysRepository,
publicInformationRepository = publicInformationRepository,
sensitiveInformationRepository = sensitiveInformationRepository,
selectedUserWalletRepository = selectedUserWalletRepository,
)
}
}

View file

@ -21,7 +21,7 @@ import com.tangem.tap.domain.userWalletList.utils.json.*
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.Provider
private const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
internal const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
fun UserWalletsListManager.Companion.provideBiometricImplementation(
applicationContext: Context,

View file

@ -0,0 +1,146 @@
package com.tangem.tap.domain.userWalletList.implementation
import com.tangem.common.CompletionResult
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import timber.log.Timber
/**
* General implementation of [UserWalletsListManager] that helps to switch between Runtime and Biometric
* implementations.
*
* @property runtimeUserWalletsListManager runtime user wallets list manager
* @property biometricUserWalletsListManager biometric user wallets list manager
* @property appPreferencesStore app preferences store
* @property dispatchers coroutine dispatcher provider
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal class GeneralUserWalletsListManager(
private val runtimeUserWalletsListManager: UserWalletsListManager,
private val biometricUserWalletsListManager: UserWalletsListManager,
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : UserWalletsListManager.Lockable {
private val applicationScope = CoroutineScope(dispatchers.io)
private val implementation = MutableStateFlow(runtimeUserWalletsListManager)
init {
subscribeOnCurrentManager()
}
override val userWallets: Flow<List<UserWallet>>
get() = implementation.flatMapLatest { it.userWallets }
override val selectedUserWallet: Flow<UserWallet>
get() = implementation.flatMapLatest { it.selectedUserWallet }
override val selectedUserWalletSync: UserWallet?
get() = implementation.value.selectedUserWalletSync
override val hasUserWallets: Boolean
get() = implementation.value.hasUserWallets
override val walletsCount: Int
get() = implementation.value.walletsCount
override val isLocked: Flow<Boolean>
get() = implementation.flatMapLatest {
if (it is UserWalletsListManager.Lockable) {
it.isLocked
} else {
error("RuntimeUserWalletsListManager is not lockable")
}
}
override val isLockedSync: Boolean
get() {
val implementation = implementation.value
return if (implementation is UserWalletsListManager.Lockable) {
implementation.isLockedSync
} else {
error("RuntimeUserWalletsListManager is not lockable")
}
}
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> {
return implementation.value.select(userWalletId)
}
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
return implementation.value.save(userWallet, canOverride)
}
override suspend fun update(
userWalletId: UserWalletId,
update: suspend (UserWallet) -> UserWallet,
): CompletionResult<UserWallet> {
return implementation.value.update(userWalletId, update)
}
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
return implementation.value.delete(userWalletIds)
}
override suspend fun clear(): CompletionResult<Unit> {
return implementation.value.clear()
}
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
return implementation.value.get(userWalletId)
}
override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult<UserWallet> {
val implementation = implementation.value
return if (implementation is UserWalletsListManager.Lockable) {
implementation.unlock()
} else {
error("RuntimeUserWalletsListManager is not lockable")
}
}
override fun lock() {
val implementation = implementation.value
return if (implementation is UserWalletsListManager.Lockable) {
implementation.lock()
} else {
error("RuntimeUserWalletsListManager is not lockable")
}
}
private fun subscribeOnCurrentManager() {
appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
.distinctUntilChanged()
.onEach { shouldSaveUserWallets ->
val manager = if (shouldSaveUserWallets) {
biometricUserWalletsListManager.copyFrom(runtimeUserWalletsListManager)
} else {
runtimeUserWalletsListManager.copyFrom(biometricUserWalletsListManager)
}
Timber.d("Switch to ${manager::class.java.simpleName}")
implementation.value = manager
}
.flowOn(dispatchers.io)
.launchIn(applicationScope)
}
/** Copy data from [old] manager and clean it */
private suspend fun UserWalletsListManager.copyFrom(old: UserWalletsListManager): UserWalletsListManager {
old.selectedUserWalletSync?.let { this.save(it) }
old.clear()
return this
}
}

View file

@ -24,10 +24,7 @@ import com.tangem.domain.wallets.legacy.isLockedSync
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@ -292,7 +289,8 @@ class DetailsMiddleware {
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
// Nothing to change
val walletsRepository = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
if (walletsRepository.shouldSaveUserWalletsSync() == enable) {
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
return@launch
@ -360,6 +358,19 @@ class DetailsMiddleware {
private suspend fun saveCurrentWallet(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
return if (featureToggles.isGeneralManagerEnabled) {
saveCurrentWalletByNewWay(scanResponse, enableAccessCodesSaving)
} else {
saveCurrentWalletByOldWay(scanResponse, enableAccessCodesSaving)
}
}
private suspend fun saveCurrentWalletByOldWay(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
val userWallet = userWalletsListManager.selectedUserWalletSync
?: scanResponse?.let { UserWalletBuilder(it).build() }
@ -381,8 +392,26 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldShowSaveUserWalletScreen = false
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = true)
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
}
.doOnFailure { error ->
Timber.e(error, "Unable to save user wallet")
}
}
private suspend fun saveCurrentWalletByNewWay(
scanResponse: ScanResponse?,
enableAccessCodesSaving: Boolean,
): CompletionResult<Unit> {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
return if (enableAccessCodesSaving) {
saveAccessCodes(scanResponse)
} else {
CompletionResult.Success(Unit)
}
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On))
}
.doOnFailure { error ->
Timber.e(error, "Unable to save user wallet")
@ -390,13 +419,22 @@ class DetailsMiddleware {
}
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
return if (featureToggles.isGeneralManagerEnabled) {
deleteSavedWalletsAndAccessCodesByNewWay()
} else {
deleteSavedWalletsAndAccessCodesByOldWay()
}
}
private suspend fun deleteSavedWalletsAndAccessCodesByOldWay(): CompletionResult<Unit> {
return userWalletsListManager.clear()
.doOnSuccess {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()
updateUserWalletsListManager(enableUserWalletsSaving = false)
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = false)
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
}
@ -405,6 +443,17 @@ class DetailsMiddleware {
}
}
private suspend fun deleteSavedWalletsAndAccessCodesByNewWay(): CompletionResult<Unit> {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
return CompletionResult.Success(Unit)
}
private fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult<Unit> {
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On))

View file

@ -16,6 +16,8 @@ internal sealed interface SaveWalletAction : Action {
data class Error(val error: TangemError) : SaveWalletAction
}
object AllowToUseBiometrics : SaveWalletAction
object Dismiss : SaveWalletAction
object CloseError : SaveWalletAction

View file

@ -18,6 +18,7 @@ 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.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
@ -42,6 +43,7 @@ internal class SaveWalletMiddleware {
private fun handleAction(action: SaveWalletAction, state: SaveWalletState) {
when (action) {
is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state)
is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state)
is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics()
is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown()
is SaveWalletAction.Dismiss -> dismiss(state)
@ -96,11 +98,16 @@ internal class SaveWalletMiddleware {
.build()
?: return@launch
provideLockableUserWalletsListManagerIfNot()
val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles)
if (featureToggles.isGeneralManagerEnabled) {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
} else {
provideLockableUserWalletsListManagerIfNot()
}
val isFirstSavedWallet = !userWalletsListManager.hasUserWallets
saveAccessCodeIfNeeded(state.backupInfo?.accessCode, userWallet.cardsInWallet)
saveAccessCodeIfNeeded(accessCode = state.backupInfo?.accessCode, cardsInWallet = userWallet.cardsInWallet)
.flatMap {
// Save wallet only at first time (SaveWalletBottomSheet).
// Otherwise (Example, add new wallet in Details) userWalletsListManager.wallets subscribers will
@ -116,16 +123,14 @@ internal class SaveWalletMiddleware {
store.dispatchWithMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = true)
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
// Enable saving access codes only if this is the first time user save the wallet
if (isFirstSavedWallet) {
preferencesStorage.shouldSaveAccessCodes = true
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
store.dispatchOnMain(SaveWalletAction.Save.Success)
@ -141,6 +146,46 @@ internal class SaveWalletMiddleware {
}
}
private fun allowToUseBiometrics(state: SaveWalletState) {
val scanResponse = state.backupInfo?.scanResponse
?: 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 = userWalletsListManager.selectedUserWalletSync
?: UserWalletBuilder(scanResponse)
.backupCardsIds(state.backupInfo?.backupCardsIds)
.build()
?: return@launch
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
saveAccessCodeIfNeeded(accessCode = state.backupInfo?.accessCode, cardsInWallet = userWallet.cardsInWallet)
.flatMap {
userWalletsListManager.save(userWallet, canOverride = true)
}
.doOnFailure { error ->
store.dispatchWithMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
preferencesStorage.shouldSaveAccessCodes = true
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
store.dispatchOnMain(SaveWalletAction.Save.Success)
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
}
}
}
private suspend fun provideLockableUserWalletsListManagerIfNot() {
if (store.state.globalState.userWalletsListManager?.isLockable == true) return

View file

@ -21,9 +21,9 @@ internal object SaveWalletReducer {
backupCardsIds = action.backupCardsIds,
),
)
is SaveWalletAction.Save -> state.copy(
isSaveInProgress = true,
)
is SaveWalletAction.Save,
is SaveWalletAction.AllowToUseBiometrics,
-> state.copy(isSaveInProgress = true)
is SaveWalletAction.Save.Error -> state.copy(
error = action.error,
isSaveInProgress = false,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.saveWallet.ui
import androidx.lifecycle.ViewModel
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.details.ui.cardsettings.TextReference
@ -19,6 +20,7 @@ import javax.inject.Inject
@HiltViewModel
internal class SaveWalletViewModel @Inject constructor(
private val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), StoreSubscriber<SaveWalletState> {
private val stateInternal = MutableStateFlow(SaveWalletScreenState())
@ -31,7 +33,12 @@ internal class SaveWalletViewModel @Inject constructor(
fun saveWallet() {
analyticsEventHandler.send(WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
store.dispatch(SaveWalletAction.Save)
if (userWalletsListManagerFeatureToggles.isGeneralManagerEnabled) {
store.dispatch(SaveWalletAction.AllowToUseBiometrics)
} else {
store.dispatch(SaveWalletAction.Save)
}
}
fun cancelOrClose() {

View file

@ -12,6 +12,8 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.UserWalletsListManagerFeatureToggles
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
@ -55,6 +57,8 @@ data class DaggerGraphState(
val currenciesRepository: CurrenciesRepository? = null,
val blockchainDataStorage: BlockchainDataStorage? = null,
val accountCreator: AccountCreator? = null,
val userWalletsListManagerFeatureToggles: UserWalletsListManagerFeatureToggles? = null,
val generalUserWalletsListManager: UserWalletsListManager? = null,
) : StateType {
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {