From 6e642be3cac80a0f12e9071f77612d90ea6f85ef Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Feb 2024 21:14:45 +0700 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../main/java/com/tangem/tap/MainActivity.kt | 15 +- .../java/com/tangem/tap/TapApplication.kt | 16 +- .../com/tangem/tap/common/extensions/Store.kt | 7 + .../tap/common/redux/global/GlobalReducer.kt | 17 +- ...ultUserWalletsListManagerFeatureToggles.kt | 12 ++ ...rWalletsListManagerFeatureTogglesModule.kt | 23 +++ .../di/UserWalletsListManagerModule.kt | 108 +++++++++++++ .../di/UserWalletsListManagerProvider.kt | 2 +- .../GeneralUserWalletsListManager.kt | 146 ++++++++++++++++++ .../details/redux/DetailsMiddleware.kt | 67 ++++++-- .../saveWallet/redux/SaveWalletAction.kt | 2 + .../saveWallet/redux/SaveWalletMiddleware.kt | 61 +++++++- .../saveWallet/redux/SaveWalletReducer.kt | 6 +- .../saveWallet/ui/SaveWalletViewModel.kt | 9 +- .../tap/proxy/redux/DaggerGraphState.kt | 4 + .../configs/feature_toggles_config.json | 4 + .../features/ExampleInstrumentedTest.kt | 22 --- .../UserWalletsListManagerFeatureToggles.kt | 6 + 19 files changed, 478 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt delete mode 100644 domain/legacy/src/androidTest/java/com/tangem/domain/features/ExampleInstrumentedTest.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2010e7ac69..4d977ecce4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,6 +116,7 @@ dependencies { implementation(deps.androidx.core.ktx) implementation(deps.androidx.core.splashScreen) implementation(deps.androidx.appCompat) + implementation(deps.androidx.datastore) implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.constraintLayout) implementation(deps.androidx.activity.compose) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 04b3f17541..116e6dcf12 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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 @@ -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) { diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index e4337c01f6..5ca956a2bf 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -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, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 79c87a7f28..d16d3488e2 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -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 Store.inject(getDependency: DaggerGraphState.() -> T?): T { + return requireNotNull(state.daggerGraphState.getDependency()) { + "${T::class.simpleName} isn't initialized " + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index bd03cf6cae..778a37590c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt new file mode 100644 index 0000000000..ca0f135159 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/DefaultUserWalletsListManagerFeatureToggles.kt @@ -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") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt new file mode 100644 index 0000000000..f94041dc73 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerFeatureTogglesModule.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt new file mode 100644 index 0000000000..1a162862e6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerModule.kt @@ -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, + ) + } +} \ No newline at end of file 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 025cb4df17..efb44252c2 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 @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt new file mode 100644 index 0000000000..cf7497ea51 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -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> + get() = implementation.flatMapLatest { it.userWallets } + + override val selectedUserWallet: Flow + 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 + 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 { + return implementation.value.select(userWalletId) + } + + override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { + return implementation.value.save(userWallet, canOverride) + } + + override suspend fun update( + userWalletId: UserWalletId, + update: suspend (UserWallet) -> UserWallet, + ): CompletionResult { + return implementation.value.update(userWalletId, update) + } + + override suspend fun delete(userWalletIds: List): CompletionResult { + return implementation.value.delete(userWalletIds) + } + + override suspend fun clear(): CompletionResult { + return implementation.value.clear() + } + + override suspend fun get(userWalletId: UserWalletId): CompletionResult { + return implementation.value.get(userWalletId) + } + + override suspend fun unlock(throwIfNotAllWalletsUnlocked: Boolean): CompletionResult { + 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 + } +} \ No newline at end of file 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 508f29e664..3d68508c7e 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 @@ -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 { + 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 { 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 { + 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 { + val featureToggles = store.inject(DaggerGraphState::userWalletsListManagerFeatureToggles) + + return if (featureToggles.isGeneralManagerEnabled) { + deleteSavedWalletsAndAccessCodesByNewWay() + } else { + deleteSavedWalletsAndAccessCodesByOldWay() + } + } + + private suspend fun deleteSavedWalletsAndAccessCodesByOldWay(): CompletionResult { 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 { + 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 { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index 00a5c7413c..934f7e8880 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -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 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 9408cbcc9e..ddf1c22d74 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 @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt index f2552462b6..b1c7269494 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt index 4c56a3b9b4..99947493fc 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt @@ -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 { 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() { diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 4a543195d6..78bec8cd19 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -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 get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index e76218c369..0180204de2 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -14,5 +14,9 @@ { "name": "REDESIGNED_SEND_SCREEN_ENABLED", "version": "undefined" + }, + { + "name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED", + "version": "5.7.0" } ] diff --git a/domain/legacy/src/androidTest/java/com/tangem/domain/features/ExampleInstrumentedTest.kt b/domain/legacy/src/androidTest/java/com/tangem/domain/features/ExampleInstrumentedTest.kt deleted file mode 100644 index 70e3d0ada2..0000000000 --- a/domain/legacy/src/androidTest/java/com/tangem/domain/features/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.domain.features - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.tangem.feature2.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt new file mode 100644 index 0000000000..d5af7e4449 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerFeatureToggles.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.wallets.legacy + +interface UserWalletsListManagerFeatureToggles { + + val isGeneralManagerEnabled: Boolean +} \ No newline at end of file