Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-07 04:58:06 +03:00
parent 5bbbd9e945
commit f74f4e7083
12 changed files with 76 additions and 89 deletions

View file

@ -35,13 +35,10 @@ import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
@ -149,7 +146,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
lockUserWalletsTimer = LockUserWalletsTimer(owner = this)
initUserWalletsListManager()
initIntentHandlers()
store.dispatch(
@ -251,18 +247,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
intentProcessor.addHandler(SellCurrencyIntentHandler())
}
private fun initUserWalletsListManager() {
val manager = if (preferencesStorage.shouldSaveUserWallets) {
UserWalletsListManager.provideBiometricImplementation(
context = applicationContext,
tangemSdkManager = tangemSdkManager,
)
} else {
UserWalletsListManager.provideRuntimeImplementation()
}
store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager))
}
private fun updateAppTheme(appThemeMode: AppThemeMode) {
MutableAppThemeModeHolder.value = appThemeMode
MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme()

View file

@ -32,6 +32,7 @@ import com.tangem.domain.common.LogConfig
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
@ -58,6 +59,8 @@ import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.domain.tokens.UserTokensStorageService
import com.tangem.tap.domain.totalBalance.TotalFiatBalanceCalculator
import com.tangem.tap.domain.totalBalance.di.provideDefaultImplementation
import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation
import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletCurrencies.di.provideDefaultImplementation
import com.tangem.tap.domain.walletStores.WalletStoresManager
@ -254,6 +257,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
initUserWalletsListManager()
loadNativeLibraries()
@ -391,4 +395,14 @@ internal class TapApplication : Application(), ImageLoaderFactory {
private fun initWarningMessagesManager() {
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager()))
}
private fun initUserWalletsListManager() {
val manager = if (preferencesStorage.shouldSaveUserWallets) {
UserWalletsListManager.provideBiometricImplementation(applicationContext)
} else {
UserWalletsListManager.provideRuntimeImplementation()
}
store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager))
}
}

View file

@ -3,27 +3,28 @@ 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.Provider
import com.tangem.common.authentication.AuthenticatedStorage
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.tap.domain.TangemSdkManager
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
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
private const val USER_WALLETS_STORAGE_NAME = "user_wallets_storage"
fun UserWalletsListManager.Companion.provideBiometricImplementation(
context: Context,
tangemSdkManager: TangemSdkManager,
applicationContext: Context,
): UserWalletsListManager {
val moshi = Moshi.Builder()
.add(WalletDerivedKeysMapAdapter())
@ -40,7 +41,7 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation(
val secureStorage = AndroidSecureStorage(
preferences = SecureStorage.createEncryptedSharedPreferences(
context = context,
context = applicationContext,
storageName = USER_WALLETS_STORAGE_NAME,
),
)
@ -48,16 +49,17 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation(
val authenticatedStorage = AuthenticatedStorage(
secureStorage = UserWalletsKeysStoreDecorator(
featureStorage = secureStorage,
cardSdkStorage = tangemSdkManager.secureStorage,
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
),
keystoreManager = DelegatedKeystoreManager(
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
),
keystoreManager = tangemSdkManager.keystoreManager,
)
val keysRepository = BiometricUserWalletsKeysRepository(
moshi = moshi,
secureStorage = secureStorage,
authenticatedStorage = authenticatedStorage,
)
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
moshi = moshi,

View file

@ -0,0 +1,18 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.Provider
import com.tangem.common.authentication.KeystoreManager
import javax.crypto.SecretKey
internal class DelegatedKeystoreManager(
private val keystoreManagerProvider: Provider<KeystoreManager>,
) : KeystoreManager {
override suspend fun authenticateAndGetKey(keyAlias: String): SecretKey? {
return keystoreManagerProvider().authenticateAndGetKey(keyAlias)
}
override suspend fun storeKey(keyAlias: String, key: SecretKey) {
keystoreManagerProvider().storeKey(keyAlias, key)
}
}

View file

@ -1,28 +1,29 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.Provider
import com.tangem.common.services.secure.SecureStorage
/**
* A decorator for [SecureStorage] that facilitates data migration between two storages.
*
* @property featureStorage The primary storage, which will eventually contain all user data.
* @property cardSdkStorage The SDK's storage where user data might have been previously stored.
* @property cardSdkStorageProvider The SDK's storage where user data might have been previously stored.
*/
internal class UserWalletsKeysStoreDecorator(
private val featureStorage: SecureStorage,
private val cardSdkStorage: SecureStorage,
private val cardSdkStorageProvider: Provider<SecureStorage>,
) : SecureStorage by featureStorage {
override fun delete(account: String) {
featureStorage.delete(account)
cardSdkStorage.delete(account)
cardSdkStorageProvider().delete(account)
}
override fun get(account: String): ByteArray? {
var data = featureStorage.get(account)
if (data == null) {
data = cardSdkStorage.get(account) ?: return null
data = cardSdkStorageProvider().get(account) ?: return null
featureStorage.store(data, account)
}

View file

@ -74,8 +74,7 @@ sealed class DetailsAction : Action {
}
data class CheckBiometricsStatus(
val awaitStatusChange: Boolean,
val lifecycleCoroutineScope: LifecycleCoroutineScope,
val lifecycleScope: LifecycleCoroutineScope,
) : AppSettings()
object EnrollBiometrics : AppSettings()

View file

@ -40,7 +40,8 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -209,6 +210,9 @@ class DetailsMiddleware {
}
class AppSettingsMiddleware {
private val checkBiometricsStatusJobHolder = JobHolder()
fun handle(state: DetailsState, action: DetailsAction.AppSettings) {
when (action) {
is DetailsAction.AppSettings.SwitchPrivacySetting -> {
@ -218,11 +222,7 @@ class DetailsMiddleware {
}
}
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
checkBiometricsStatus(
awaitStatusChange = action.awaitStatusChange,
state = state,
lifecycleScope = action.lifecycleCoroutineScope,
)
observeBiometricsStatusChanges(state, action.lifecycleScope)
}
is DetailsAction.AppSettings.EnrollBiometrics -> {
enrollBiometrics()
@ -245,27 +245,20 @@ class DetailsMiddleware {
}
}
/**
* @param awaitStatusChange If true then start a new coroutine and check the biometric status every 100
* milliseconds until it changes
* */
private fun checkBiometricsStatus(
awaitStatusChange: Boolean,
state: DetailsState,
lifecycleScope: LifecycleCoroutineScope,
) {
lifecycleScope.launch {
if (awaitStatusChange) {
while (state.appSettingsState.needEnrollBiometrics == tangemSdkManager.needEnrollBiometrics) {
delay(timeMillis = 100)
private fun observeBiometricsStatusChanges(state: DetailsState, lifecycleScope: LifecycleCoroutineScope) {
lifecycleScope.launch(Dispatchers.IO) {
do {
val needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull()
if (needEnrollBiometrics != null &&
needEnrollBiometrics != state.appSettingsState.needEnrollBiometrics
) {
store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics))
}
}
store.dispatchWithMain(
DetailsAction.AppSettings.BiometricsStatusChanged(
needEnrollBiometrics = tangemSdkManager.needEnrollBiometrics,
),
)
}
delay(timeMillis = 500)
} while (true)
}.saveIn(checkBiometricsStatusJobHolder)
}
private fun enrollBiometrics() {
@ -454,10 +447,7 @@ class DetailsMiddleware {
return null
}
return UserWalletsListManager.provideBiometricImplementation(
context = context,
tangemSdkManager = tangemSdkManager,
)
return UserWalletsListManager.provideBiometricImplementation(context)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.details.ui.appsettings
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
@ -32,11 +31,6 @@ internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsS
AppSettingsViewModel(store, detailsFeatureToggles, appCurrencyRepository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.checkBiometricsStatus(lifecycleScope)
}
@Composable
override fun ScreenContent(modifier: Modifier) {
AppSettingsScreen(
@ -49,6 +43,11 @@ internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsS
)
}
override fun onResume() {
super.onResume()
viewModel.checkBiometricsStatus(lifecycleScope)
}
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
@ -58,11 +57,6 @@ internal class AppSettingsFragment : ComposeFragment(), StoreSubscriber<DetailsS
}
}
override fun onResume() {
super.onResume()
viewModel.refreshBiometricsStatus(lifecycleScope)
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)

View file

@ -55,21 +55,7 @@ internal class AppSettingsViewModel(
}
fun checkBiometricsStatus(lifecycleScope: LifecycleCoroutineScope) {
store.dispatch(
DetailsAction.AppSettings.CheckBiometricsStatus(
awaitStatusChange = false,
lifecycleCoroutineScope = lifecycleScope,
),
)
}
fun refreshBiometricsStatus(lifecycleScope: LifecycleCoroutineScope) {
store.dispatch(
DetailsAction.AppSettings.CheckBiometricsStatus(
awaitStatusChange = true,
lifecycleCoroutineScope = lifecycleScope,
),
)
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(lifecycleScope))
}
private fun buildItems(state: AppSettingsState): ImmutableList<AppSettingsScreenState.Item> {

View file

@ -140,10 +140,7 @@ internal class SaveWalletMiddleware {
store.dispatchWithMain(SaveWalletAction.Save.Error(TangemSdkError.ExceptionError(error)))
return
}
val manager = UserWalletsListManager.provideBiometricImplementation(
context = context,
tangemSdkManager = tangemSdkManager,
)
val manager = UserWalletsListManager.provideBiometricImplementation(context)
store.dispatchWithMain(GlobalAction.UpdateUserWalletsListManager(manager))
}

View file

@ -9,6 +9,7 @@ import com.tangem.domain.txhistory.repository.TxHistoryRepository
class GetTxHistoryItemsCountUseCase(private val repository: TxHistoryRepository) {
// FIXME: Provide UserWalletId
suspend operator fun invoke(network: Network): Either<TxHistoryStateError, Int> {
return either {
catch(

View file

@ -14,6 +14,7 @@ private const val DEFAULT_PAGE_SIZE = 50
class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) {
// FIXME: Provide UserWalletId
operator fun invoke(
currency: CryptoCurrency,
pageSize: Int = DEFAULT_PAGE_SIZE,