Updated on 2026-08-14
This commit is contained in:
parent
bb95420224
commit
143dcc5a62
34 changed files with 632 additions and 114 deletions
|
|
@ -28,6 +28,7 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
|||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -38,7 +39,9 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
|
|
@ -142,4 +145,10 @@ interface ApplicationEntryPoint {
|
|||
fun getApiConfigsManager(): ApiConfigsManager
|
||||
|
||||
fun getUserTokensResponseStore(): UserTokensResponseStore
|
||||
|
||||
fun getUserWalletsListRepository(): UserWalletsListRepository
|
||||
|
||||
fun getTangemHotSdk(): TangemHotSdk
|
||||
|
||||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||
}
|
||||
|
|
@ -227,6 +227,15 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
private val userTokensResponseStore: UserTokensResponseStore
|
||||
get() = entryPoint.getUserTokensResponseStore()
|
||||
|
||||
private val userWalletsListRepository
|
||||
get() = entryPoint.getUserWalletsListRepository()
|
||||
|
||||
private val tangemHotSdk
|
||||
get() = entryPoint.getTangemHotSdk()
|
||||
|
||||
private val hotWalletFeatureToggles
|
||||
get() = entryPoint.getHotWalletFeatureToggles()
|
||||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
|
@ -364,6 +373,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
uiMessageSender = uiMessageSender,
|
||||
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
|
||||
userTokensResponseStore = userTokensResponseStore,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,10 +26,9 @@ internal object LegacyMiddleware {
|
|||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
userWalletsListManager.selectedUserWallet
|
||||
selectedUserWallet()
|
||||
.distinctUntilChanged()
|
||||
.onEach { selectedUserWallet ->
|
||||
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
||||
|
|
@ -52,6 +51,16 @@ internal object LegacyMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun selectedUserWallet(): Flow<UserWallet> {
|
||||
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
store.inject(DaggerGraphState::userWalletsListRepository).selectedUserWallet.filterNotNull()
|
||||
} else {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.selectedUserWallet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGACY: We need to initialize [AppSettingsState] async to avoid drawing blocking
|
||||
* previously it was initialized in runBlocking and blocked details screen
|
||||
|
|
@ -64,6 +73,8 @@ internal object LegacyMiddleware {
|
|||
selectedAppCurrency = store.state.globalState.appCurrency,
|
||||
selectedThemeMode = store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull()
|
||||
?: AppThemeMode.DEFAULT,
|
||||
requireAccessCode = store.inject(DaggerGraphState::walletsRepository).requireAccessCode(),
|
||||
useBiometricAuthentication = store.inject(DaggerGraphState::walletsRepository).useBiometricAuthentication(),
|
||||
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
|
||||
.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
||||
|
|
|
|||
|
|
@ -123,10 +123,12 @@ internal object WalletsDomainModule {
|
|||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
walletsRepository: WalletsRepository,
|
||||
): SaveWalletUseCase {
|
||||
return SaveWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletsRepository = walletsRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ internal object UserWalletsListManagerModule {
|
|||
@ApplicationContext applicationContext: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
passwordRequester: HotWalletPasswordRequester,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): UserWalletsListRepository {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
|
@ -162,8 +163,8 @@ internal object UserWalletsListManagerModule {
|
|||
passwordRequester = passwordRequester,
|
||||
userWalletEncryptionKeysRepository = userWalletEncryptionKeysRepository,
|
||||
tangemSdkManagerProvider = Provider { tangemSdkManager },
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
||||
// TODO add a settings toggle to disable saving persistent information
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import com.tangem.common.doOnFailure
|
|||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.map
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -43,6 +46,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
private val userWalletEncryptionKeysRepository: UserWalletEncryptionKeysRepository,
|
||||
private val tangemSdkManagerProvider: Provider<TangemSdkManager>,
|
||||
private val savePersistentInformation: ProviderSuspend<Boolean>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : UserWalletsListRepository {
|
||||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||
|
|
@ -129,38 +133,46 @@ internal class DefaultUserWalletsListRepository(
|
|||
userWallet
|
||||
}
|
||||
|
||||
override suspend fun setLock(userWalletId: UserWalletId, lockMethod: LockMethod): Either<SetLockError, Unit> =
|
||||
either {
|
||||
val userWallet = userWallets.value?.find { it.walletId == userWalletId }
|
||||
?: raise(SetLockError.UserWalletNotFound)
|
||||
override suspend fun setLock(
|
||||
userWalletId: UserWalletId,
|
||||
lockMethod: LockMethod,
|
||||
changeUnsecured: Boolean,
|
||||
): Either<SetLockError, Unit> = either {
|
||||
val userWallet = userWallets.value?.find { it.walletId == userWalletId }
|
||||
?: raise(SetLockError.UserWalletNotFound)
|
||||
|
||||
val encryptionKey = userWallet.encryptionKey
|
||||
?: raise(SetLockError.UserWalletLocked)
|
||||
val encryptionKey = userWallet.encryptionKey
|
||||
?: raise(SetLockError.UserWalletLocked)
|
||||
|
||||
runCatching {
|
||||
userWalletEncryptionKeysRepository.save(
|
||||
encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWalletId,
|
||||
encryptionKey = encryptionKey,
|
||||
),
|
||||
method = when (lockMethod) {
|
||||
is LockMethod.AccessCode -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Password(lockMethod.accessCode)
|
||||
runCatching {
|
||||
userWalletEncryptionKeysRepository.save(
|
||||
encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWalletId,
|
||||
encryptionKey = encryptionKey,
|
||||
),
|
||||
removeUnsecured = changeUnsecured,
|
||||
method = when (lockMethod) {
|
||||
is LockMethod.AccessCode -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Password(lockMethod.accessCode)
|
||||
}
|
||||
LockMethod.Biometric -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Biometric
|
||||
}
|
||||
LockMethod.NoLock -> {
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
raise(SetLockError.UserWalletNotFound)
|
||||
}
|
||||
LockMethod.Biometric -> {
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Biometric
|
||||
}
|
||||
LockMethod.NoLock -> {
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
raise(SetLockError.UserWalletNotFound)
|
||||
}
|
||||
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Unsecured
|
||||
}
|
||||
},
|
||||
)
|
||||
}.onFailure { raise(SetLockError.UnableToSetLock(it)) }
|
||||
}
|
||||
UserWalletEncryptionKeysRepository.EncryptionMethod.Unsecured
|
||||
}
|
||||
},
|
||||
)
|
||||
}.onFailure { raise(SetLockError.UnableToSetLock(it)) }
|
||||
}
|
||||
|
||||
override suspend fun removeBiometricLock(userWalletId: UserWalletId) {
|
||||
userWalletEncryptionKeysRepository.removeBiometricKey(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): Either<DeleteWalletError, Unit> = either {
|
||||
if (userWalletIds.isEmpty()) return Unit.right()
|
||||
|
|
@ -269,9 +281,11 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
|
||||
val unsecuredKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
val allKeys = biometricKeys + unsecuredKeys
|
||||
val allKeys = (biometricKeys + unsecuredKeys).distinct()
|
||||
val unlockedWallets = allKeys.map { it.walletId }
|
||||
|
||||
if (allKeys.all { it.walletId in userWalletIds }.not()) {
|
||||
// if we cant unlock all wallets
|
||||
if (userWalletIds.all { it in unlockedWallets }.not()) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +323,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
biometryFallback: suspend () -> Either<UnlockWalletError, Unit>,
|
||||
): Either<UnlockWalletError, UserWalletEncryptionKey?> {
|
||||
val result = passwordRequester.requestPassword(
|
||||
hasBiometry = tangemSdkManagerProvider.invoke().canUseBiometry,
|
||||
hasBiometry = hasBiometry(),
|
||||
)
|
||||
|
||||
return when (result) {
|
||||
|
|
@ -339,6 +353,15 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun hasBiometry(): Boolean {
|
||||
val useBiometricAuthentication = appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
|
||||
default = false,
|
||||
)
|
||||
|
||||
return tangemSdkManagerProvider.invoke().canUseBiometry && useBiometricAuthentication
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
|
|
|
|||
|
|
@ -25,8 +25,14 @@ internal class UserWalletEncryptionKeysRepository(
|
|||
Types.newParameterizedType(List::class.java, UserWalletId::class.java),
|
||||
)
|
||||
|
||||
suspend fun save(encryptionKey: UserWalletEncryptionKey, method: EncryptionMethod) = withContext(dispatchers.io) {
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyUnsecured(encryptionKey.walletId).name)
|
||||
suspend fun save(
|
||||
encryptionKey: UserWalletEncryptionKey,
|
||||
removeUnsecured: Boolean = true,
|
||||
method: EncryptionMethod,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (removeUnsecured) {
|
||||
secureStorage.delete(StorageKey.UserWalletEncryptionKeyUnsecured(encryptionKey.walletId).name)
|
||||
}
|
||||
|
||||
when (method) {
|
||||
EncryptionMethod.Unsecured -> {
|
||||
|
|
@ -35,12 +41,6 @@ internal class UserWalletEncryptionKeysRepository(
|
|||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
EncryptionMethod.Biometric -> {
|
||||
authenticatedStorage.store(
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
is EncryptionMethod.Password -> {
|
||||
val encodedWithPass = AESEncryptionProtocol.encryptWithPassword(
|
||||
password = method.password,
|
||||
|
|
@ -51,11 +51,21 @@ internal class UserWalletEncryptionKeysRepository(
|
|||
data = encodedWithPass,
|
||||
)
|
||||
}
|
||||
EncryptionMethod.Biometric -> {
|
||||
authenticatedStorage.store(
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
storeUserWalletId(userWalletId = encryptionKey.walletId)
|
||||
}
|
||||
|
||||
fun removeBiometricKey(userWalletId: UserWalletId) {
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
suspend fun getAllUnsecured(): List<UserWalletEncryptionKey> = withContext(dispatchers.io) {
|
||||
getUserWalletsIds().mapNotNull { userWalletId ->
|
||||
secureStorage.get(account = StorageKey.UserWalletEncryptionKeyUnsecured(userWalletId).name).decodeToKey()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -64,6 +66,14 @@ class DetailsMiddleware {
|
|||
when (action.setting) {
|
||||
AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
||||
AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
||||
AppSetting.RequireAccessCode -> toggleRequireAccessCode(
|
||||
state = state,
|
||||
enable = action.enable,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(
|
||||
state = state,
|
||||
enable = action.enable,
|
||||
)
|
||||
}
|
||||
}
|
||||
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
|
||||
|
|
@ -90,6 +100,91 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun toggleBiometricsAuthentication(state: DetailsState, enable: Boolean) {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
// Nothing to change
|
||||
if (walletsRepository.useBiometricAuthentication() == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
toggleRequireAccessCode(
|
||||
state = state,
|
||||
enable = true,
|
||||
)
|
||||
|
||||
if (enable) {
|
||||
setBiometricLockForAllWallets()
|
||||
} else {
|
||||
// Remove all biometric-related data
|
||||
removeAllBiometricData()
|
||||
}
|
||||
|
||||
walletsRepository.setUseBiometricAuthentication(value = enable)
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleRequireAccessCode(state: DetailsState, enable: Boolean) {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
// Nothing to change
|
||||
if (walletsRepository.requireAccessCode() == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
// Remove all biometric sign data
|
||||
removeAllBiometricSingData()
|
||||
toggleSaveAccessCodes(state, enable = false)
|
||||
} else {
|
||||
toggleSaveAccessCodes(state, enable = true)
|
||||
}
|
||||
|
||||
walletsRepository.setRequireAccessCode(value = enable)
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setBiometricLockForAllWallets() {
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
userWallets.forEach {
|
||||
userWalletsListRepository.setLock(
|
||||
userWalletId = it.walletId,
|
||||
lockMethod = LockMethod.Biometric,
|
||||
changeUnsecured = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun removeAllBiometricData() {
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
userWalletsListRepository.userWalletsSync().forEach {
|
||||
userWalletsListRepository.removeBiometricLock(it.walletId)
|
||||
}
|
||||
removeAllBiometricSingData()
|
||||
}
|
||||
|
||||
private suspend fun removeAllBiometricSingData() {
|
||||
deleteSavedAccessCodes()
|
||||
val userWalletsListRepository = store.inject(DaggerGraphState::userWalletsListRepository)
|
||||
val tangemHotSdk = store.inject(DaggerGraphState::tangemHotSdk)
|
||||
userWalletsListRepository.userWalletsSync().forEach {
|
||||
if (it is UserWallet.Hot) {
|
||||
userWalletsListRepository.saveWithoutLock(
|
||||
userWallet = it.copy(
|
||||
hotWalletId = tangemHotSdk.removeBiometryAuthIfPresented(it.hotWalletId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeBiometricsStatusChanges(scope: CoroutineScope) {
|
||||
val needEnrollBiometricsFlow = flow {
|
||||
do {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy(
|
||||
|
|
@ -46,6 +47,14 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
saveWallets = true, // User can't enable access codes saving without wallets saving
|
||||
saveAccessCodes = action.enable,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
requireAccessCode = action.enable,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
useBiometricAuthentication = action.enable,
|
||||
)
|
||||
},
|
||||
)
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success -> state.copy(
|
||||
|
|
@ -63,6 +72,14 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
isInProgress = false,
|
||||
saveAccessCodes = action.prevState,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
requireAccessCode = action.prevState,
|
||||
)
|
||||
AppSetting.BiometricAuthentication -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
needEnrollBiometrics = action.prevState,
|
||||
)
|
||||
},
|
||||
)
|
||||
is DetailsAction.AppSettings.BiometricsStatusChanged -> state.copy(
|
||||
|
|
|
|||
|
|
@ -12,9 +12,14 @@ data class DetailsState(
|
|||
) : StateType
|
||||
|
||||
data class AppSettingsState(
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveWallets: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveAccessCodes: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val isBiometricsAvailable: Boolean = false,
|
||||
val requireAccessCode: Boolean = false,
|
||||
val useBiometricAuthentication: Boolean = false,
|
||||
val needEnrollBiometrics: Boolean = false,
|
||||
val isHidingEnabled: Boolean = false,
|
||||
val isInProgress: Boolean = false,
|
||||
|
|
@ -25,5 +30,5 @@ data class AppSettingsState(
|
|||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
||||
enum class AppSetting {
|
||||
SaveWallets, SaveAccessCode
|
||||
SaveWallets, SaveAccessCode, RequireAccessCode, BiometricAuthentication,
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Dialog
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -55,4 +56,37 @@ internal class AppSettingsDialogsFactory {
|
|||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDisableBiometricAuthenticationAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(
|
||||
R.string.app_settings_off_biometrics_alert_message,
|
||||
wrappedList(resourceReference(R.string.common_biometrics)),
|
||||
),
|
||||
confirmText = resourceReference(R.string.common_disable),
|
||||
onConfirm = onDisable,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createEnableRequireAccessCodeAlert(onEnable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_on_require_access_code_alert_message),
|
||||
confirmText = resourceReference(R.string.common_enable),
|
||||
onConfirm = { onEnable() },
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDisableRequireAccessCodeAlert(onDisable: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_off_require_access_code_alert_message),
|
||||
confirmText = resourceReference(R.string.common_disable),
|
||||
onConfirm = { onDisable() },
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.details.ui.appsettings
|
|||
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -33,6 +34,39 @@ internal class AppSettingsItemsFactory {
|
|||
)
|
||||
}
|
||||
|
||||
fun createUseBiometricsSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = ID_USE_BIOMETRICS_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_enable_biometrics_title),
|
||||
description = resourceReference(
|
||||
R.string.app_settings_biometrics_footer,
|
||||
wrappedList(resourceReference(R.string.common_biometrics)),
|
||||
),
|
||||
isEnabled = isEnabled,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
fun createRequireAccessCodeSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = ID_REQUIRE_ACCESS_CODE_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_require_access_code),
|
||||
description = resourceReference(R.string.app_settings_require_access_code_footer),
|
||||
isEnabled = isEnabled,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
fun createSaveAccessCodeSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
|
|
@ -96,5 +130,7 @@ internal class AppSettingsItemsFactory {
|
|||
const val ID_FLIP_TO_HIDE_BALANCE_SWITCH = "flip_to_hide_balance_switch"
|
||||
const val ID_SELECT_APP_CURRENCY_BUTTON = "select_app_currency_button"
|
||||
const val ID_SELECT_THEME_MODE_BUTTON = "select_theme_mode_button"
|
||||
const val ID_USE_BIOMETRICS_SWITCH = "use_biometrics_switch"
|
||||
const val ID_REQUIRE_ACCESS_CODE_SWITCH = "require_access_code_switch"
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
|||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -51,6 +52,7 @@ internal class AppSettingsModel @Inject constructor(
|
|||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : Model(), StoreSubscriber<DetailsState> {
|
||||
|
||||
private val itemsFactory = AppSettingsItemsFactory()
|
||||
|
|
@ -109,20 +111,36 @@ internal class AppSettingsModel @Inject constructor(
|
|||
onClick = ::showAppCurrencySelector,
|
||||
).let(::add)
|
||||
|
||||
if (state.isBiometricsAvailable) {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
||||
|
||||
itemsFactory.createSaveWalletsSwitch(
|
||||
isChecked = state.saveWallets,
|
||||
itemsFactory.createUseBiometricsSwitch(
|
||||
isChecked = state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveWalletsToggled,
|
||||
onCheckedChange = ::onBiometricAuthenticationToggled,
|
||||
).let(::add)
|
||||
|
||||
itemsFactory.createSaveAccessCodeSwitch(
|
||||
isChecked = state.saveAccessCodes,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveAccessCodesToggled,
|
||||
itemsFactory.createRequireAccessCodeSwitch(
|
||||
isChecked = state.requireAccessCode,
|
||||
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
||||
onCheckedChange = ::onRequireAccessCodeToggled,
|
||||
).let(::add)
|
||||
} else {
|
||||
if (state.isBiometricsAvailable) {
|
||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
||||
|
||||
itemsFactory.createSaveWalletsSwitch(
|
||||
isChecked = state.saveWallets,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveWalletsToggled,
|
||||
).let(::add)
|
||||
|
||||
itemsFactory.createSaveAccessCodeSwitch(
|
||||
isChecked = state.saveAccessCodes,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveAccessCodesToggled,
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
|
||||
itemsFactory.createFlipToHideBalanceSwitch(
|
||||
|
|
@ -168,6 +186,56 @@ internal class AppSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onBiometricAuthenticationToggled(isChecked: Boolean) {
|
||||
// TODO : Uncomment and implement analytics event when ready
|
||||
// val param = AnalyticsParam.OnOffState(isChecked)
|
||||
// analyticsEventHandler.send(Settings.AppSettings.BiometricAuthenticationChanged(param))
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = true)
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
} else {
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = dialogsFactory.createDisableBiometricAuthenticationAlert(
|
||||
onDisable = {
|
||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = false)
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRequireAccessCodeToggled(isChecked: Boolean) {
|
||||
// TODO : Uncomment and implement analytics event when ready
|
||||
// val param = AnalyticsParam.OnOffState(isChecked)
|
||||
// analyticsEventHandler.send(Settings.AppSettings.RequireAccessCodeChanged(param))
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = if (isChecked) {
|
||||
dialogsFactory.createEnableRequireAccessCodeAlert(
|
||||
onEnable = {
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
)
|
||||
} else {
|
||||
dialogsFactory.createDisableRequireAccessCodeAlert(
|
||||
onDisable = {
|
||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = false)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSaveWalletsToggled(isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.SaveWallets, enable = true)
|
||||
|
|
@ -236,6 +304,8 @@ internal class AppSettingsModel @Inject constructor(
|
|||
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),
|
||||
isBiometricsAvailable = canUseBiometryUseCase(),
|
||||
useBiometricAuthentication = walletsRepository.useBiometricAuthentication(),
|
||||
requireAccessCode = walletsRepository.requireAccessCode(),
|
||||
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
|
||||
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.domain.card.ScanCardProcessor
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.feedback.GetCardInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -31,7 +32,9 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
|
|
@ -77,4 +80,7 @@ data class DaggerGraphState(
|
|||
val cardArworksProvider: CardArtworksProvider? = null,
|
||||
val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null,
|
||||
val userTokensResponseStore: UserTokensResponseStore? = null,
|
||||
val userWalletsListRepository: UserWalletsListRepository? = null,
|
||||
val hotWalletFeatureToggles: HotWalletFeatureToggles? = null,
|
||||
val tangemHotSdk: TangemHotSdk? = null,
|
||||
) : StateType
|
||||
|
|
@ -84,6 +84,10 @@ object PreferencesKeys {
|
|||
|
||||
val SHOULD_SAVE_ACCESS_CODES_KEY by lazy { booleanPreferencesKey(name = "saveAccessCodes") }
|
||||
|
||||
val REQUIRE_ACCESS_CODE_KEY by lazy { booleanPreferencesKey(name = "requireAccessCode") }
|
||||
|
||||
val USE_BIOMETRIC_AUTHENTICATION_KEY by lazy { booleanPreferencesKey(name = "useBiometricAuthentication") }
|
||||
|
||||
val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") }
|
||||
|
||||
val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
|
||||
|
|
|
|||
|
|
@ -947,9 +947,13 @@
|
|||
<string name="send_validation_invalid_fee">Die Gebühr geht über die Bilanz hinaus</string>
|
||||
<string name="send_validation_invalid_total">Der Gesamtbetrag geht über die Bilanz hinaus</string>
|
||||
<string name="send_with_swap_confirm_title">Tauschen und senden</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">Mit der Konvertierung fortfahren? Dadurch werden Deine vorherigen Daten gelöscht.</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">Das Senden einer anderen Währung führt zu deren unwiderruflichem Verlust.</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">Wähle das richtige Empfängernetzwerk</string>
|
||||
<string name="send_with_swap_notification_text">Sende uns ein Token, und wir konvertieren es unterwegs. Dein Empfänger erhält genau das, was er braucht – nahtlos.</string>
|
||||
<string name="send_with_swap_recipient_amount_text">Wird an den Empfänger gesendet</string>
|
||||
<string name="send_with_swap_recipient_amount_title">Zu erhaltender Betrag</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">Möchtest Du die Konvertierung wirklich abbrechen? Deine bisherigen Daten werden gelöscht.</string>
|
||||
<string name="send_with_swap_title">Senden mit Swap</string>
|
||||
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
|
||||
<string name="settings_card_settings_footer">Bereite das Scannen der Karte oder Ring vor, die du einrichten möchtest.</string>
|
||||
|
|
@ -1408,7 +1412,7 @@
|
|||
<string name="wc_alert_unsupported_method_title">Wir haben einen unbekannten Fehler festgestellt.</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht.</string>
|
||||
<string name="wc_alert_unsupported_networks_title">Nicht unterstützte Netzwerke</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem unterstützt ein erforderliches Netzwerk um %s</string>
|
||||
<string name="wc_alert_verified_domain_description">Diese Domain hat die Überprüfungen bestanden und gilt als sicher, vertrauenswürdig und frei von bekannten Bedrohungen oder verdächtigen Aktivitäten. %s</string>
|
||||
<string name="wc_alert_verified_domain_title">Verifizierte Domain</string>
|
||||
<string name="wc_alert_wrong_card_description">Falsche Karte oder falscher Ring in der App ausgewählt</string>
|
||||
<string name="wc_alert_wrong_card_title">Wir haben eine Art Problem</string>
|
||||
|
|
@ -1432,7 +1436,7 @@
|
|||
<string name="wc_copy_data_button_text">Daten kopieren</string>
|
||||
<string name="wc_custom_allowance_title">Benutzerdefinierter Freibetrag</string>
|
||||
<string name="wc_disconnect_all">Alle trennen</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Text über die Trennung aller dApps</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Alle dApp-Sitzungen werden getrennt. Ihre Wallet wird nicht mehr mit dApps verbunden sein.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Alle dApps trennen</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Versuchen Sie erneut, mit einer neuen URI zu koppeln</string>
|
||||
<string name="wc_errors_invalid_domain_title">Ungültige dApp-Domain</string>
|
||||
|
|
|
|||
|
|
@ -1354,7 +1354,7 @@
|
|||
<string name="wc_alert_unsupported_method_title">Hemos encontrado un error desconocido</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Actualmente, Tangem no es compatible con una red requerida por %s.</string>
|
||||
<string name="wc_alert_unsupported_networks_title">Redes no compatibles</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem soporta una red requerida por %s</string>
|
||||
<string name="wc_alert_verified_domain_description">Este dominio ha superado las verificaciones y se considera seguro, confiable y libre de amenazas conocidas o actividades sospechosas. %s</string>
|
||||
<string name="wc_alert_verified_domain_title">Dominio verificado</string>
|
||||
<string name="wc_alert_wrong_card_description">Se seleccionó una tarjeta o un anillo incorrectos en la app</string>
|
||||
<string name="wc_alert_wrong_card_title">Tenemos algún tipo de problema</string>
|
||||
|
|
@ -1382,7 +1382,7 @@
|
|||
<string name="wc_custom_allowance_title">Asignación personalizada</string>
|
||||
<string name="wc_dapp_disconnected">dApp desconectada</string>
|
||||
<string name="wc_disconnect_all">Desconectar todo</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Texto sobre desconexión de todas las dApps</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Todas las sesiones de dApp se desconectarán. Su billetera ya no estará vinculada a ninguna dApp.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Desconectar todas las dApps</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Intente emparejar nuevamente con una URI nueva</string>
|
||||
<string name="wc_errors_invalid_domain_title">Dominio de dApp no válido</string>
|
||||
|
|
@ -1418,7 +1418,7 @@
|
|||
<string name="wc_unlimited_amount">Cantidad ilimitada</string>
|
||||
<string name="wc_uri_already_used_description">Asegúrese de que cada intento de emparejamiento utiliza un URI nuevo y único</string>
|
||||
<string name="wc_uri_already_used_title">URI ya utilizado</string>
|
||||
<string name="wc_wallet_connect">Wallet connect</string>
|
||||
<string name="wc_wallet_connect">WalletConnect</string>
|
||||
<string name="wc_warning_transaction">Transacción sospechosa</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Ignorar</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">Tiene un backup interrumpido. ¿Quiere reanudarlo?</string>
|
||||
|
|
|
|||
|
|
@ -1324,6 +1324,7 @@
|
|||
<string name="wc_alert_unsupported_dapps_title">dApp non prise en charge</string>
|
||||
<string name="wc_alert_unsupported_method_description">Code d\'erreur : 8 005. Si le problème persiste, n\'hésitez pas à contacter notre support.</string>
|
||||
<string name="wc_alert_unsupported_method_title">Nous avons rencontré une erreur inconnue</string>
|
||||
<string name="wc_alert_verified_domain_description">Ce domaine a passé les contrôles de vérification et est considéré comme sûr, fiable et exempt de menaces connues ou d’activités suspectes.%s</string>
|
||||
<string name="wc_allow_to_spend">Autoriser à dépenser</string>
|
||||
<string name="wc_common_address">Adresse</string>
|
||||
<string name="wc_common_loading">Chargement</string>
|
||||
|
|
@ -1333,7 +1334,7 @@
|
|||
<string name="wc_contents">Contenu</string>
|
||||
<string name="wc_copy_data_button_text">Copier les données</string>
|
||||
<string name="wc_disconnect_all">Déconnecter tout</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Texte sur la déconnexion de toutes les dApps</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Toutes les sessions dApp seront déconnectées. Votre portefeuille ne sera plus lié à aucune dApp.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Déconnecter toutes les dApps</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Essayez de jumeler à nouveau avec un nouvel URI</string>
|
||||
<string name="wc_errors_invalid_domain_title">Domaine dApp invalide</string>
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@
|
|||
<string name="access_code_create_description">ウォレットのロックを解除するには、 %s桁のアクセスコードを設定します。</string>
|
||||
<string name="access_code_create_title">アクセスコードの作成</string>
|
||||
<string name="access_code_navtitle">アクセスコード</string>
|
||||
<string name="account_archived_recover">回復する</string>
|
||||
<string name="account_archived_title">アーカイブ済み</string>
|
||||
<string name="account_details_archive">アカウントをアーカイブする</string>
|
||||
<string name="account_details_archive_action">アーカイブ</string>
|
||||
<string name="account_details_archive_description">このアカウントをアーカイブしますが、いつでも復元できます。</string>
|
||||
<string name="account_details_title">アカウント</string>
|
||||
<string name="account_form_account_index">アカウント番号%s — アドレス導出に使用されます。</string>
|
||||
<string name="account_form_create_button">アカウントを追加</string>
|
||||
<string name="account_form_edit_button">保存</string>
|
||||
<string name="account_form_name">アカウント名</string>
|
||||
|
|
@ -287,6 +290,7 @@
|
|||
<string name="common_transaction_status">取引状況</string>
|
||||
<string name="common_transactions">取引</string>
|
||||
<string name="common_transfer">送金</string>
|
||||
<string name="common_unable_to_load">データを読み込めません…</string>
|
||||
<string name="common_understand">わかりました</string>
|
||||
<string name="common_unknown_error">エラーが発生しました。もう一度お試しください。</string>
|
||||
<string name="common_unreachable">アクセスできません</string>
|
||||
|
|
@ -984,10 +988,13 @@
|
|||
<string name="send_with_swap_confirm_title">スワップして送信</string>
|
||||
<string name="send_with_swap_convert_token_alert_message">変換を続行しますか? これにより以前のデータは消去されます。</string>
|
||||
<string name="send_with_swap_convert_token_alert_title">変換を確定</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_message">その他の通貨を送信すると、取り返しのつかない損失が発生します。</string>
|
||||
<string name="send_with_swap_correct_recipient_network_notification_title">正しい受信者ネットワークを選択してください</string>
|
||||
<string name="send_with_swap_notification_text">トークンを送信すれば、送信中に変換されます。受信者は必要なものをシームレスに受け取ります。</string>
|
||||
<string name="send_with_swap_recipient_amount_success_title">受信者は受け取ります</string>
|
||||
<string name="send_with_swap_recipient_amount_text">受取人へ</string>
|
||||
<string name="send_with_swap_recipient_amount_title">受取金額</string>
|
||||
<string name="send_with_swap_recipient_get_amount">受信者は%sを取得します</string>
|
||||
<string name="send_with_swap_remove_convert_alert_message">変換をキャンセルしてもよろしいですか?以前のデータは消去されます。</string>
|
||||
<string name="send_with_swap_remove_convert_alert_title">変換を削除</string>
|
||||
<string name="send_with_swap_title">スワップして送信</string>
|
||||
|
|
@ -1448,7 +1455,7 @@
|
|||
<string name="wc_alert_unsupported_method_title">不明なエラーが発生しました</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Tangemは現在%sで必要なネットワークをサポートしていません。</string>
|
||||
<string name="wc_alert_unsupported_networks_title">未対応のネットワーク</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangemは%sで必要なネットワークをサポートします</string>
|
||||
<string name="wc_alert_verified_domain_description">このドメインは検証チェックに合格しており、安全で信頼でき、既知の脅威や不審な活動がないと判断されています。%s</string>
|
||||
<string name="wc_alert_verified_domain_title">検証済みドメイン</string>
|
||||
<string name="wc_alert_wrong_card_description">アプリで間違ったカードまたはリングが選択されました</string>
|
||||
<string name="wc_alert_wrong_card_title">問題が起きています</string>
|
||||
|
|
@ -1476,7 +1483,7 @@
|
|||
<string name="wc_custom_allowance_title">使用可能量の設定</string>
|
||||
<string name="wc_dapp_disconnected">dAppが接続解除されました</string>
|
||||
<string name="wc_disconnect_all">すべての接続を解除する</string>
|
||||
<string name="wc_disconnect_all_alert_desc">すべてのdAppsの接続解除に関するテキスト</string>
|
||||
<string name="wc_disconnect_all_alert_desc">すべてのdAppセッションが切断されます。ウォレットはどのdAppにも接続されなくなります。</string>
|
||||
<string name="wc_disconnect_all_alert_title">すべてのdAppを接続解除する</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">新しいURIで、再度ペアリングを試してください</string>
|
||||
<string name="wc_errors_invalid_domain_title">無効なdAppドメイン</string>
|
||||
|
|
@ -1512,7 +1519,7 @@
|
|||
<string name="wc_unlimited_amount">無制限</string>
|
||||
<string name="wc_uri_already_used_description">各ペアリング試行で、新しくユニークなURIが使用されていることを確認します</string>
|
||||
<string name="wc_uri_already_used_title">URIはすでに使用されています</string>
|
||||
<string name="wc_wallet_connect">ウォレットコネクト</string>
|
||||
<string name="wc_wallet_connect">WalletConnect</string>
|
||||
<string name="wc_warning_transaction">不審な取引</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">破棄</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">バックアップが中断されました。再開しますか?</string>
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@
|
|||
<string name="common_transaction_status">Статус транзакции</string>
|
||||
<string name="common_transactions">Транзакции</string>
|
||||
<string name="common_transfer">Перевод</string>
|
||||
<string name="common_unable_to_load">Невозможно загрузить данные…</string>
|
||||
<string name="common_understand">Я понял</string>
|
||||
<string name="common_unknown_error">Произошла ошибка. Пожалуйста, попробуйте снова.</string>
|
||||
<string name="common_unreachable">Недоступно</string>
|
||||
|
|
@ -1317,12 +1318,12 @@
|
|||
<string name="wc_alert_unknown_error_description">Код ошибки: %s. Если проблема сохраняется, обратитесь в нашу службу поддержки.</string>
|
||||
<string name="wc_alert_unknown_error_description_no_error_code">Если проблема сохраняется, обратитесь в нашу службу поддержки</string>
|
||||
<string name="wc_alert_unknown_error_title">Мы обнаружили неизвестную ошибку</string>
|
||||
<string name="wc_alert_unsupported_dapps_description">Кошелек Tangem.в настоящий момент не поддерживает %s</string>
|
||||
<string name="wc_alert_unsupported_dapps_description">Кошелек Tangem в настоящий момент не поддерживает %s</string>
|
||||
<string name="wc_alert_unsupported_dapps_title">Неподдерживаемый dApp</string>
|
||||
<string name="wc_alert_unsupported_method_title">Мы обнаружили неизвестную ошибку</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Tangem в настоящее время не поддерживает необходимую сеть для %s</string>
|
||||
<string name="wc_alert_unsupported_networks_title">Неподдерживаемые сети</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem поддерживает сеть, необходимую для %s</string>
|
||||
<string name="wc_alert_verified_domain_description">Этот домен прошёл проверку и считается безопасным, надёжным и свободным от известных угроз или подозрительной активности. %s</string>
|
||||
<string name="wc_alert_verified_domain_title">Верифицированный домен</string>
|
||||
<string name="wc_alert_wrong_card_description">Выбрана не верная карта или кольцо</string>
|
||||
<string name="wc_alert_wrong_card_title">Похоже, возникла проблема</string>
|
||||
|
|
@ -1350,6 +1351,7 @@
|
|||
<string name="wc_custom_allowance_title">Настраиваемый лимит</string>
|
||||
<string name="wc_dapp_disconnected">dApp отключен</string>
|
||||
<string name="wc_disconnect_all">Отключить все</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Все сессии dApp будут отключены. Ваш кошелёк больше не будет связан ни с одним dApp.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Отключить все dApp</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Попробуйте соединиться снова, используя новый URI</string>
|
||||
<string name="wc_errors_invalid_domain_title">Недействительный домен dApp</string>
|
||||
|
|
@ -1384,7 +1386,7 @@
|
|||
<string name="wc_unlimited_amount">Безлимитное количество</string>
|
||||
<string name="wc_uri_already_used_description">Убедитесь, что каждая попытка сопряжения использует новый и уникальный URI.</string>
|
||||
<string name="wc_uri_already_used_title">URI уже используется</string>
|
||||
<string name="wc_wallet_connect">Подключение кошелька</string>
|
||||
<string name="wc_wallet_connect">WalletConnect</string>
|
||||
<string name="wc_warning_transaction">Подозрительная транзакция</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Отказаться</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">Вы не закончили резервное копирование. Хотите продолжить?</string>
|
||||
|
|
|
|||
|
|
@ -1304,7 +1304,7 @@
|
|||
<string name="wc_alert_unknown_error_title">Ми зіткнулися з невідомою помилкою</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Tangem наразі не підтримує необхідну мережу для %s.</string>
|
||||
<string name="wc_alert_unsupported_networks_title">Непідтримувані мережі</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem підтримує мережу, необхідну для %s</string>
|
||||
<string name="wc_alert_verified_domain_description">Цей домен пройшов перевірку та вважається безпечним, надійним і вільним від відомих загроз чи підозрілої активності. %s</string>
|
||||
<string name="wc_alert_verified_domain_title">Верифікований домен</string>
|
||||
<string name="wc_alert_wrong_card_description">Обрана не вірна картка або кільце</string>
|
||||
<string name="wc_alert_wrong_card_title">Схоже, виникла проблема</string>
|
||||
|
|
@ -1328,7 +1328,7 @@
|
|||
<string name="wc_copy_data_button_text">Копіювати дані</string>
|
||||
<string name="wc_dapp_disconnected">dApp відключено</string>
|
||||
<string name="wc_disconnect_all">Розʼєднати все</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Відключити всі dApps</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Усі сесії dApp буде відключено. Ваш гаманець більше не буде пов’язаний із жодним dApp.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Відключити всі dApps</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Спробуйте ще раз з новим URI</string>
|
||||
<string name="wc_errors_invalid_domain_title">Недійсний домен dApp</string>
|
||||
|
|
|
|||
|
|
@ -71,8 +71,10 @@
|
|||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
<string name="app_settings_enable_biometrics_title">Enable biometric authentication</string>
|
||||
<string name="app_settings_off_biometrics_alert_message">Disabling %1$s will require you to enter your passcode to unlock the app and to interact with your wallet.</string>
|
||||
<string name="app_settings_off_require_access_code_alert_message">You’ll be asked for your wallet’s access code later so we can securely store it for future use</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">This will delete all the saved wallet access codes. Any further operation with the wallet will require submitting the access code.</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">Removing the saved devices deletes all the saved wallets and their access codes from the app.</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code.</string>
|
||||
<string name="app_settings_require_access_code">Require Access Code</string>
|
||||
<string name="app_settings_require_access_code_footer">This option disables biometric authentication for sensitive actions. You will be required to enter your access code every time, such as when signing a transaction.</string>
|
||||
<string name="app_settings_saved_access_codes">Save Access Code</string>
|
||||
|
|
@ -297,6 +299,7 @@
|
|||
<string name="common_transaction_status">Transaction status</string>
|
||||
<string name="common_transactions">Transactions</string>
|
||||
<string name="common_transfer">Transfer</string>
|
||||
<string name="common_unable_to_load">Unable to load the data…</string>
|
||||
<string name="common_understand">I understand</string>
|
||||
<string name="common_unknown_error">There was an error. Please try again.</string>
|
||||
<string name="common_unreachable">Unreachable</string>
|
||||
|
|
@ -1519,7 +1522,7 @@
|
|||
<string name="wc_alert_unsupported_method_title">We\'ve encountered unknown error</string>
|
||||
<string name="wc_alert_unsupported_networks_description">Tangem does not currently support a required network by %s.</string>
|
||||
<string name="wc_alert_unsupported_networks_title">Unsupported networks</string>
|
||||
<string name="wc_alert_verified_domain_description">Tangem support a required network by %s</string>
|
||||
<string name="wc_alert_verified_domain_description">This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s</string>
|
||||
<string name="wc_alert_verified_domain_title">Verified domain</string>
|
||||
<string name="wc_alert_wrong_card_description">Wrong card or ring selected in the App</string>
|
||||
<string name="wc_alert_wrong_card_title">We\'ve got some kind of problem</string>
|
||||
|
|
@ -1547,7 +1550,7 @@
|
|||
<string name="wc_custom_allowance_title">Custom allowance</string>
|
||||
<string name="wc_dapp_disconnected">dApp disconnected</string>
|
||||
<string name="wc_disconnect_all">Disconnect all</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Text about discnected all dApps</string>
|
||||
<string name="wc_disconnect_all_alert_desc">All dApp sessions will be disconnected. Your wallet will no longer be linked to any dApps.</string>
|
||||
<string name="wc_disconnect_all_alert_title">Disconect All dApps</string>
|
||||
<string name="wc_errors_invalid_domain_subtitle">Try pairing again with a fresh URI</string>
|
||||
<string name="wc_errors_invalid_domain_title">Invalid dApp domain</string>
|
||||
|
|
@ -1584,7 +1587,7 @@
|
|||
<string name="wc_unlimited_amount">Unlimited Amount</string>
|
||||
<string name="wc_uri_already_used_description">Ensure that each pairing attempt uses a fresh and unique URI</string>
|
||||
<string name="wc_uri_already_used_title">URI already used</string>
|
||||
<string name="wc_wallet_connect">Wallet connect</string>
|
||||
<string name="wc_wallet_connect">WalletConnect</string>
|
||||
<string name="wc_warning_transaction">Suspicious transaction</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.SEED_FIRST_NOTIFI
|
|||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectMap
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -47,14 +48,78 @@ internal class DefaultWalletsRepository(
|
|||
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
|
||||
}
|
||||
|
||||
@Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method")
|
||||
override fun shouldSaveUserWallets(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
|
||||
}
|
||||
|
||||
@Deprecated("Hot wallet feature makes app always save user wallets. Do not use this method")
|
||||
override suspend fun saveShouldSaveUserWallets(item: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, value = item)
|
||||
}
|
||||
|
||||
override suspend fun useBiometricAuthentication(): Boolean {
|
||||
val useBiometricAuthentication = appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
|
||||
)
|
||||
|
||||
if (useBiometricAuthentication != null) {
|
||||
return useBiometricAuthentication
|
||||
}
|
||||
|
||||
val legacySaveWalletsInTheApp = appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.SAVE_USER_WALLETS_KEY,
|
||||
)
|
||||
|
||||
if (legacySaveWalletsInTheApp != null) {
|
||||
// Migrate legacy setting to new one
|
||||
appPreferencesStore.store(
|
||||
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
|
||||
value = legacySaveWalletsInTheApp,
|
||||
)
|
||||
return legacySaveWalletsInTheApp
|
||||
} else {
|
||||
// Default value for new users
|
||||
setUseBiometricAuthentication(false)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setUseBiometricAuthentication(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY, value = value)
|
||||
}
|
||||
|
||||
override suspend fun requireAccessCode(): Boolean {
|
||||
val requireAccessCode = appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY,
|
||||
)
|
||||
|
||||
if (requireAccessCode != null) {
|
||||
return requireAccessCode
|
||||
}
|
||||
|
||||
val legacyShouldSaveAccessCode = appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY,
|
||||
)
|
||||
|
||||
if (legacyShouldSaveAccessCode != null) {
|
||||
// Migrate legacy setting to new one
|
||||
appPreferencesStore.store(
|
||||
key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY,
|
||||
value = legacyShouldSaveAccessCode.not(),
|
||||
)
|
||||
return legacyShouldSaveAccessCode.not()
|
||||
} else {
|
||||
// Default value for new users
|
||||
setRequireAccessCode(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setRequireAccessCode(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY, value = value)
|
||||
}
|
||||
|
||||
override suspend fun isWalletWithRing(userWalletId: UserWalletId): Boolean {
|
||||
return appPreferencesStore
|
||||
.getSyncOrDefault(key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY, default = emptySet())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.data.wallets.hot
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.copy
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.exception.WrongPasswordException
|
||||
import com.tangem.hot.sdk.model.*
|
||||
|
|
@ -9,7 +13,9 @@ import javax.inject.Inject
|
|||
|
||||
class HotWalletAccessor @Inject constructor(
|
||||
private val tangemHotSdk: TangemHotSdk,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletPasswordRequester: HotWalletPasswordRequester,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List<DataToSign>): List<SignedData> =
|
||||
|
|
@ -23,10 +29,18 @@ class HotWalletAccessor @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun <T> hotSdkRequest(hotWalletId: HotWalletId, block: suspend (unlock: UnlockHotWallet) -> T): T {
|
||||
val isAccessCodeRequired = walletsRepository.requireAccessCode()
|
||||
|
||||
val auth = when (hotWalletId.authType) {
|
||||
HotWalletId.AuthType.NoPassword -> HotAuth.NoAuth
|
||||
HotWalletId.AuthType.Password -> requestPassword(false)
|
||||
HotWalletId.AuthType.Biometry -> HotAuth.Biometry
|
||||
HotWalletId.AuthType.Biometry -> {
|
||||
if (isAccessCodeRequired) {
|
||||
requestPassword(false)
|
||||
} else {
|
||||
HotAuth.Biometry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runCatchingSdkErrors(hotWalletId, auth) {
|
||||
|
|
@ -47,20 +61,41 @@ class HotWalletAccessor @Inject constructor(
|
|||
block = { blockAuth ->
|
||||
block(blockAuth).also {
|
||||
// Update biometry auth if the original auth was password
|
||||
if (blockAuth is HotAuth.Password) {
|
||||
tangemHotSdk.changeAuth(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = hotWalletId,
|
||||
auth = blockAuth,
|
||||
),
|
||||
auth = HotAuth.Biometry,
|
||||
)
|
||||
}
|
||||
updateBiometryAuthIfNeeded(
|
||||
hotWalletId = hotWalletId,
|
||||
originalAuth = blockAuth,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateBiometryAuthIfNeeded(hotWalletId: HotWalletId, originalAuth: HotAuth) {
|
||||
val isAccessCodeRequired = walletsRepository.requireAccessCode()
|
||||
|
||||
if (originalAuth is HotAuth.Password && isAccessCodeRequired.not()) {
|
||||
val userWallet = userWalletsListRepository.userWalletsSync()
|
||||
.find { it is UserWallet.Hot && it.hotWalletId == hotWalletId }
|
||||
as? UserWallet.Hot
|
||||
?: return
|
||||
|
||||
val newHotWalletId = tangemHotSdk.changeAuth(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = hotWalletId,
|
||||
auth = originalAuth,
|
||||
),
|
||||
auth = HotAuth.Biometry,
|
||||
)
|
||||
|
||||
userWalletsListRepository.saveWithoutLock(
|
||||
userWallet = userWallet.copy(
|
||||
hotWalletId = newHotWalletId,
|
||||
),
|
||||
canOverride = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> runCatchingWrongPassInternal(
|
||||
originalAuth: HotAuth,
|
||||
auth: HotAuth,
|
||||
|
|
|
|||
|
|
@ -73,8 +73,21 @@ interface UserWalletsListRepository {
|
|||
* If the wallet is not found, it returns [SetLockError.UserWalletNotFound]
|
||||
* If the wallet is locked, it returns [SetLockError.UserWalletLocked]
|
||||
* If the lock method is not supported, it returns [SetLockError.UnableToSetLock].
|
||||
*
|
||||
* @param userWalletId The ID of the user wallet to set the lock for.
|
||||
* @param lockMethod The method to use for locking the wallet.
|
||||
* @param changeUnsecured If false, the method will have no effect on unsecured wallets.
|
||||
*/
|
||||
suspend fun setLock(userWalletId: UserWalletId, lockMethod: LockMethod): Either<SetLockError, Unit>
|
||||
suspend fun setLock(
|
||||
userWalletId: UserWalletId,
|
||||
lockMethod: LockMethod,
|
||||
changeUnsecured: Boolean = true,
|
||||
): Either<SetLockError, Unit>
|
||||
|
||||
/**
|
||||
* Removes biometric lock for user wallet if it is set.
|
||||
*/
|
||||
suspend fun removeBiometricLock(userWalletId: UserWalletId)
|
||||
|
||||
/**
|
||||
* Deletes user wallets by ids.
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ interface SettingsRepository {
|
|||
|
||||
suspend fun setShouldOpenWelcomeScreenOnResume(value: Boolean)
|
||||
|
||||
@Deprecated("Use walletsRepository.requireAccessCode instead")
|
||||
suspend fun shouldSaveAccessCodes(): Boolean
|
||||
|
||||
@Deprecated("Use walletsRepository.requireAccessCode instead")
|
||||
suspend fun setShouldSaveAccessCodes(value: Boolean)
|
||||
|
||||
suspend fun incrementAppLaunchCounter()
|
||||
|
|
|
|||
|
|
@ -11,10 +11,20 @@ interface WalletsRepository {
|
|||
|
||||
suspend fun shouldSaveUserWalletsSync(): Boolean
|
||||
|
||||
@Deprecated("Hot wallet make always save user wallets. Do not use this method")
|
||||
fun shouldSaveUserWallets(): Flow<Boolean>
|
||||
|
||||
@Deprecated("Hot wallet make always save user wallets. Do not use this method")
|
||||
suspend fun saveShouldSaveUserWallets(item: Boolean)
|
||||
|
||||
suspend fun useBiometricAuthentication(): Boolean
|
||||
|
||||
suspend fun setUseBiometricAuthentication(value: Boolean)
|
||||
|
||||
suspend fun requireAccessCode(): Boolean
|
||||
|
||||
suspend fun setRequireAccessCode(value: Boolean)
|
||||
|
||||
suspend fun isWalletWithRing(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun setHasWalletsWithRing(userWalletId: UserWalletId)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import arrow.core.raise.either
|
|||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.error.SaveWalletError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
/**
|
||||
* Use case for saving user wallet
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.domain.core.wallets.UserWalletsListRepository
|
|||
class SaveWalletUseCase(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val useNewRepository: Boolean,
|
||||
) {
|
||||
|
||||
|
|
@ -35,10 +37,14 @@ class SaveWalletUseCase(
|
|||
if (newUserWallet) {
|
||||
when (userWallet) {
|
||||
is UserWallet.Cold -> {
|
||||
userWalletsListRepository.setLock(
|
||||
userWallet.walletId,
|
||||
UserWalletsListRepository.LockMethod.Biometric,
|
||||
)
|
||||
if (walletsRepository.useBiometricAuthentication()) {
|
||||
userWalletsListRepository.setLock(
|
||||
userWallet.walletId,
|
||||
UserWalletsListRepository.LockMethod.Biometric,
|
||||
)
|
||||
} else {
|
||||
Unit.right()
|
||||
}
|
||||
}
|
||||
is UserWallet.Hot -> {
|
||||
userWalletsListRepository.setLock(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
api(projects.features.biometry.api)
|
||||
implementation(projects.features.hotWallet.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.ui)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.biometry.impl.ui.state.AskBiometryUM
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -45,6 +47,8 @@ internal class AskBiometryModel @Inject constructor(
|
|||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsManager: SettingsManager,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<AskBiometryComponent.Params>()
|
||||
|
|
@ -109,10 +113,18 @@ internal class AskBiometryModel @Inject constructor(
|
|||
walletsRepository.saveShouldSaveUserWallets(item = true)
|
||||
settingsRepository.setShouldSaveAccessCodes(value = true)
|
||||
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
walletsRepository.setUseBiometricAuthentication(value = true)
|
||||
setBiometryLockForAllWallets()
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = userWallet.hasAccessCode,
|
||||
isBiometricsRequestPolicy = walletsRepository.requireAccessCode().not(),
|
||||
)
|
||||
} else {
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
cardSdkConfigRepository.setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = userWallet.hasAccessCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (_uiState.value.bottomSheetVariant) {
|
||||
|
|
@ -123,6 +135,18 @@ internal class AskBiometryModel @Inject constructor(
|
|||
params.modelCallbacks.onAllowed()
|
||||
}
|
||||
|
||||
private fun setBiometryLockForAllWallets() {
|
||||
modelScope.launch {
|
||||
userWalletsListRepository.userWalletsSync().forEach { userWallet ->
|
||||
userWalletsListRepository.setLock(
|
||||
userWalletId = userWallet.walletId,
|
||||
lockMethod = UserWalletsListRepository.LockMethod.Biometric,
|
||||
changeUnsecured = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEnrollBiometricsDialog() {
|
||||
uiMessageSender.send(
|
||||
DialogMessage(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.hotwallet.accesscode.entity.AccessCodeUM
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
|
|
@ -28,6 +29,7 @@ internal class AccessCodeModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val tangemHotSdk: TangemHotSdk,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -85,13 +87,15 @@ internal class AccessCodeModel @Inject constructor(
|
|||
auth = HotAuth.Password(accessCode.toCharArray()),
|
||||
)
|
||||
|
||||
updatedHotWalletId = tangemHotSdk.changeAuth(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = updatedHotWalletId,
|
||||
auth = HotAuth.Password(accessCode.toCharArray()),
|
||||
),
|
||||
auth = HotAuth.Biometry,
|
||||
)
|
||||
if (walletsRepository.requireAccessCode().not()) {
|
||||
updatedHotWalletId = tangemHotSdk.changeAuth(
|
||||
unlockHotWallet = UnlockHotWallet(
|
||||
walletId = updatedHotWalletId,
|
||||
auth = HotAuth.Password(accessCode.toCharArray()),
|
||||
),
|
||||
auth = HotAuth.Biometry,
|
||||
)
|
||||
}
|
||||
|
||||
userWalletsListRepository.saveWithoutLock(
|
||||
userWallet.copy(
|
||||
|
|
@ -106,10 +110,12 @@ internal class AccessCodeModel @Inject constructor(
|
|||
UserWalletsListRepository.LockMethod.AccessCode(accessCode.toCharArray()),
|
||||
)
|
||||
|
||||
userWalletsListRepository.setLock(
|
||||
userWallet.walletId,
|
||||
UserWalletsListRepository.LockMethod.Biometric,
|
||||
)
|
||||
if (walletsRepository.useBiometricAuthentication()) {
|
||||
userWalletsListRepository.setLock(
|
||||
userWallet.walletId,
|
||||
UserWalletsListRepository.LockMethod.Biometric,
|
||||
)
|
||||
}
|
||||
|
||||
params.callbacks.onAccessCodeConfirmed(params.userWalletId)
|
||||
}.onFailure {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ internal class AddExistingWalletImportModel @Inject constructor(
|
|||
val hotWalletId = tangemHotSdk.importWallet(mnemonic, passphrase?.toCharArray(), HotAuth.NoAuth)
|
||||
val hotUserWalletBuilder = hotUserWalletBuilderFactory.create(hotWalletId)
|
||||
val userWallet = hotUserWalletBuilder.build()
|
||||
saveUserWalletUseCase(userWallet)
|
||||
saveUserWalletUseCase(userWallet.copy(backedUp = true))
|
||||
params.callbacks.onWalletImported(userWallet.walletId)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.models.wallet.isLocked
|
|||
import com.tangem.domain.core.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.core.wallets.error.UnlockWalletError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.GetIsBiometricsEnabledUseCase
|
||||
import com.tangem.features.wallet.utils.UserWalletsFetcher
|
||||
import com.tangem.features.welcome.impl.R
|
||||
|
|
@ -35,6 +36,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class WelcomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -43,6 +45,7 @@ internal class WelcomeModel @Inject constructor(
|
|||
private val userWalletsFetcherFactory: UserWalletsFetcher.Factory,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val getIsBiometricsEnabledUseCase: GetIsBiometricsEnabledUseCase,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) : Model() {
|
||||
|
||||
// TODO add intent handling
|
||||
|
|
@ -154,8 +157,7 @@ internal class WelcomeModel @Inject constructor(
|
|||
when (option) {
|
||||
Create -> router.push(AppRoute.CreateWalletSelection)
|
||||
Add -> router.push(AppRoute.AddExistingWallet)
|
||||
Buy -> {
|
||||
}
|
||||
Buy -> Unit // TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,8 +184,8 @@ internal class WelcomeModel @Inject constructor(
|
|||
unlockWallet(userWallet.walletId, unlockMethod)
|
||||
}
|
||||
|
||||
private fun canUnlockWithBiometrics(): Boolean {
|
||||
return getIsBiometricsEnabledUseCase.canUseBiometry()
|
||||
private suspend fun canUnlockWithBiometrics(): Boolean {
|
||||
return getIsBiometricsEnabledUseCase.canUseBiometry() && walletsRepository.useBiometricAuthentication()
|
||||
}
|
||||
|
||||
suspend fun unlockWallet(userWalletId: UserWalletId, unlockMethod: UserWalletsListRepository.UnlockMethod) {
|
||||
|
|
|
|||
|
|
@ -70,16 +70,18 @@ internal fun AnimatedContentScope.WelcomeSelectWallet(state: WelcomeUM.SelectWal
|
|||
|
||||
BottomFade(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
.animateEnterExit(fadeIn(), fadeOut()),
|
||||
text = "Unlock all with biometric",
|
||||
onClick = state.onUnlockWithBiometricClick,
|
||||
)
|
||||
if (state.showUnlockWithBiometricButton) {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.navigationBarsPadding()
|
||||
.animateEnterExit(fadeIn(), fadeOut()),
|
||||
text = "Unlock all with biometric",
|
||||
onClick = state.onUnlockWithBiometricClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.wallets) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue