Updated on 2026-08-14
This commit is contained in:
commit
f28fd90fa3
35 changed files with 480 additions and 240 deletions
|
|
@ -117,6 +117,8 @@ object TangemSdkErrorMapper {
|
|||
is TangemSdkError.BiometricsAuthenticationLockout -> error
|
||||
is TangemSdkError.BiometricsAuthenticationPermanentLockout -> error
|
||||
is TangemSdkError.UserCanceledBiometricsAuthentication -> error
|
||||
is TangemSdkError.EncryptionOperationFailed -> error
|
||||
is TangemSdkError.InvalidEncryptionKey -> error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed class UserWalletListError(code: Int) : TangemError(code) {
|
||||
override val silent: Boolean
|
||||
get() = (cause as? TangemError)?.silent == true
|
||||
|
||||
override val messageResId: Int? = null
|
||||
|
||||
object WalletAlreadySaved : UserWalletListError(code = 60001) {
|
||||
override var customMessage: String = "This wallet has already been saved, you can add another one"
|
||||
override val messageResId: Int = R.string.user_wallet_list_error_wallet_already_saved
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.domain.userWalletList
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.wallet.R
|
||||
|
||||
sealed class UserWalletsListError(code: Int) : TangemError(code) {
|
||||
override val silent: Boolean
|
||||
get() = (cause as? TangemError)?.silent == true
|
||||
|
||||
override val messageResId: Int? = null
|
||||
|
||||
object WalletAlreadySaved : UserWalletsListError(code = 60001) {
|
||||
override var customMessage: String = "This wallet has already been saved, you can add another one"
|
||||
override val messageResId: Int = R.string.user_wallet_list_error_wallet_already_saved
|
||||
}
|
||||
|
||||
object InvalidEncryptionKey : UserWalletsListError(code = 60002) {
|
||||
override var customMessage: String = "Invalid encryption key"
|
||||
}
|
||||
|
||||
data class BiometricsAuthenticationLockout(val isPermanent: Boolean) : UserWalletsListError(code = 60003) {
|
||||
override var customMessage: String = "Biometric authentication lockout, permanent: $isPermanent"
|
||||
}
|
||||
|
||||
data class UnableToUnlockUserWallets(override val cause: Throwable? = null) : UserWalletsListError(code = 60004) {
|
||||
override var customMessage: String = "An error has occurred, please scan your card to log in"
|
||||
override val messageResId: Int = R.string.user_wallet_list_error_unable_to_unlock
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ interface UserWalletsListManager {
|
|||
/**
|
||||
* Save provided user wallet and set it as selected
|
||||
* @param userWallet [UserWallet] to save
|
||||
* @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries
|
||||
* @param canOverride If false, then terminate with [UserWalletsListError.WalletAlreadySaved] when user tries
|
||||
* to save an already saved card
|
||||
* @return [CompletionResult] of operation
|
||||
*/
|
||||
|
|
@ -47,7 +47,7 @@ interface UserWalletsListManager {
|
|||
|
||||
/**
|
||||
* Same as [save] but not change selected user wallet ID
|
||||
* and not terminate with [UserWalletListError.WalletAlreadySaved] if [UserWallet] already saved
|
||||
* and not terminate with [UserWalletsListError.WalletAlreadySaved] if [UserWallet] already saved
|
||||
*
|
||||
* Can terminate with [NoSuchElementException] if unable to find [UserWallet] with provided [UserWalletId]
|
||||
* @param userWalletId update [UserWallet] with that [UserWalletId]
|
||||
|
|
@ -108,7 +108,7 @@ interface UserWalletsListManager {
|
|||
* @return [CompletionResult] of operation, with selected [UserWallet]
|
||||
* or null if there is no selected [UserWallet]
|
||||
* */
|
||||
suspend fun unlock(): CompletionResult<UserWallet?>
|
||||
suspend fun unlock(): CompletionResult<UserWallet>
|
||||
|
||||
/**
|
||||
* Remove [UserWallet]s from [userWallets] and set [isLocked] as true
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ val UserWalletsListManager.isLockable: Boolean
|
|||
*
|
||||
* @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] returns [Flow] which
|
||||
* produces only one false value
|
||||
*
|
||||
* @see UserWalletsListManager.Lockable.isLockedSync
|
||||
* */
|
||||
val UserWalletsListManager.isLocked: Flow<Boolean>
|
||||
get() = asLockable()?.isLocked ?: flowOf(false)
|
||||
|
|
@ -24,6 +26,8 @@ val UserWalletsListManager.isLocked: Flow<Boolean>
|
|||
* Indicates that the [UserWalletsListManager] is locked
|
||||
*
|
||||
* @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable] returns false
|
||||
*
|
||||
* @see UserWalletsListManager.Lockable.isLockedSync
|
||||
* */
|
||||
val UserWalletsListManager.isLockedSync: Boolean
|
||||
get() = asLockable()?.isLockedSync ?: false
|
||||
|
|
@ -32,15 +36,22 @@ val UserWalletsListManager.isLockedSync: Boolean
|
|||
* Call [UserWalletsListManager.Lockable.unlock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable]
|
||||
*
|
||||
* @return If [UserWalletsListManager] not implements [UserWalletsListManager.Lockable]
|
||||
* returns [CompletionResult.Success] with [UserWalletsListManager.selectedUserWalletSync]
|
||||
* returns [CompletionResult.Failure] with [UserWalletsListError.UnableToUnlockUserWallets]
|
||||
*
|
||||
* If [UserWalletsListManager] implements [UserWalletsListManager.Lockable]
|
||||
* returns [CompletionResult.Success] with selected [UserWallet]
|
||||
*
|
||||
* @see UserWalletsListManager.Lockable.unlock
|
||||
* */
|
||||
suspend fun UserWalletsListManager.unlockIfLockable(): CompletionResult<UserWallet?> {
|
||||
return asLockable()?.unlock() ?: CompletionResult.Success(selectedUserWalletSync)
|
||||
suspend fun UserWalletsListManager.unlockIfLockable(): CompletionResult<UserWallet> {
|
||||
return asLockable()?.unlock() ?: CompletionResult.Failure(UserWalletsListError.UnableToUnlockUserWallets())
|
||||
}
|
||||
|
||||
/**
|
||||
* Call [UserWalletsListManager.Lockable.lock] if [UserWalletsListManager] implements [UserWalletsListManager.Lockable]
|
||||
* or do nothing otherwise
|
||||
*
|
||||
* @see UserWalletsListManager.Lockable.lock
|
||||
* */
|
||||
fun UserWalletsListManager.lockIfLockable() {
|
||||
asLockable()?.lock()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
|
||||
|
|
@ -15,7 +16,6 @@ import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
|||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class BiometricUserWalletsListManager(
|
||||
|
|
@ -53,9 +53,32 @@ internal class BiometricUserWalletsListManager(
|
|||
override val hasUserWallets: Boolean
|
||||
get() = keysRepository.hasSavedEncryptionKeys()
|
||||
|
||||
override suspend fun unlock(): CompletionResult<UserWallet?> {
|
||||
override suspend fun unlock(): CompletionResult<UserWallet> {
|
||||
// Occurs when user has locked user wallets after unlocking with biometry
|
||||
// e.g. after biometric storage master key invalidation
|
||||
if (state.value.hasLockedUserWalletsAfterUnlock) {
|
||||
return CompletionResult.Failure(
|
||||
error = UserWalletsListError.UnableToUnlockUserWallets(
|
||||
cause = IllegalStateException("Permanently locked"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return unlockWithBiometryInternal()
|
||||
.map { selectedUserWalletSync }
|
||||
.mapFailure { error ->
|
||||
if (error is UserWalletsListError) {
|
||||
error
|
||||
} else {
|
||||
UserWalletsListError.UnableToUnlockUserWallets(cause = error)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
selectedUserWalletSync.guard {
|
||||
throw UserWalletsListError.UnableToUnlockUserWallets(
|
||||
cause = IllegalStateException("No user wallet selected"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
|
|
@ -88,7 +111,7 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true)
|
||||
}
|
||||
|
|
@ -188,8 +211,10 @@ internal class BiometricUserWalletsListManager(
|
|||
.flatMap { loadModels() }
|
||||
.map {
|
||||
state.update { prevState ->
|
||||
val hasLockedUserWallets = prevState.userWallets.any { it.isLocked }
|
||||
prevState.copy(
|
||||
isLocked = false,
|
||||
isLocked = hasLockedUserWallets,
|
||||
hasLockedUserWalletsAfterUnlock = hasLockedUserWallets,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -234,7 +259,13 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to load user wallets")
|
||||
if (error is UserWalletsListError.InvalidEncryptionKey) {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
hasLockedUserWalletsAfterUnlock = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +321,7 @@ internal class BiometricUserWalletsListManager(
|
|||
|
||||
private fun findSelectedUserWallet(userWallets: List<UserWallet> = state.value.userWallets): UserWallet? {
|
||||
return userWallets.firstOrNull {
|
||||
it.walletId == state.value.selectedUserWalletId
|
||||
it.walletId == state.value.selectedUserWalletId && !it.isLocked
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,5 +330,6 @@ internal class BiometricUserWalletsListManager(
|
|||
val userWallets: List<UserWallet> = emptyList(),
|
||||
val selectedUserWalletId: UserWalletId? = null,
|
||||
val isLocked: Boolean = true,
|
||||
val hasLockedUserWalletsAfterUnlock: Boolean = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.CompletionResult
|
|||
import com.tangem.common.catching
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -49,7 +49,7 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
val isWalletSaved = state.value.userWallet?.walletId == userWallet.walletId
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import com.tangem.common.biometric.BiometricManager
|
|||
import com.tangem.common.biometric.BiometricStorage
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.flatMapOnFailure
|
||||
import com.tangem.common.fold
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.mapFailure
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -37,6 +40,16 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
override suspend fun getAll(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getAllInternal()
|
||||
.mapFailure { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.BiometricsAuthenticationLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = false)
|
||||
is TangemSdkError.BiometricsAuthenticationPermanentLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
|
||||
is TangemSdkError.InvalidEncryptionKey -> UserWalletsListError.InvalidEncryptionKey
|
||||
else -> error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,12 +92,28 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
return getUserWalletsIds()
|
||||
.map { userWalletId ->
|
||||
// This is possible because the Card SDK cipher key has an expiration time
|
||||
// If this operation runs more than that expiration time, the user will not receive all encryption keys
|
||||
// If this operation runs more than that expiration time, the user will have to re-authorize
|
||||
// to receive all keys
|
||||
getEncryptionKey(userWalletId)
|
||||
.flatMapOnFailure { error ->
|
||||
// If key decryption failed then skip it
|
||||
if (error is TangemSdkError.EncryptionOperationFailed) {
|
||||
CompletionResult.Success(data = null)
|
||||
} else {
|
||||
CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
// If the user cancels biometric authentication, cancel the request for all keys
|
||||
if (error is TangemSdkError.UserCanceledBiometricsAuthentication) {
|
||||
return CompletionResult.Failure(error)
|
||||
when (error) {
|
||||
is TangemSdkError.UserCanceledBiometricsAuthentication -> {
|
||||
// If the user cancels biometric authentication, cancel the request for all keys
|
||||
return CompletionResult.Failure(error)
|
||||
}
|
||||
is TangemSdkError.InvalidEncryptionKey -> {
|
||||
if (error.isKeyRegenerated) {
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,20 +123,20 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
}
|
||||
|
||||
private suspend fun getEncryptionKey(userWalletId: UserWalletId): CompletionResult<UserWalletEncryptionKey?> {
|
||||
return biometricStorage.get(StorageKey.WalletEncryptionKey(userWalletId).name)
|
||||
return biometricStorage.get(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
.map { it.decodeToKey() }
|
||||
}
|
||||
|
||||
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return biometricStorage.store(
|
||||
key = StorageKey.WalletEncryptionKey(encryptionKey.walletId).name,
|
||||
key = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
.map { storeUserWalletId(encryptionKey.walletId) }
|
||||
}
|
||||
|
||||
private suspend fun deleteEncryptionKey(userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
return biometricStorage.delete(StorageKey.WalletEncryptionKey(userWalletId).name)
|
||||
return biometricStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
private suspend fun getUserWalletsIds(): List<UserWalletId> {
|
||||
|
|
@ -175,7 +204,7 @@ internal class BiometricUserWalletsKeysRepository(
|
|||
private sealed interface StorageKey {
|
||||
val name: String
|
||||
|
||||
class WalletEncryptionKey(userWalletId: UserWalletId) : StorageKey {
|
||||
class UserWalletEncryptionKey(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@ import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.common.analytics.events.MyWallets
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.walletSelector.ui.components.BiometricsLockoutDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.BiometricsLockoutWarningContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.KeyInvalidatedWarningContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.RemoveWalletDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.RenameWalletDialogContent
|
||||
import com.tangem.tap.features.walletSelector.ui.components.WalletSelectorScreenContent
|
||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
|
||||
internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<WalletSelectorScreenState>() {
|
||||
private val viewModel by viewModels<WalletSelectorViewModel>()
|
||||
|
|
@ -93,7 +95,8 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment<Wa
|
|||
when (dialog) {
|
||||
is DialogModel.RemoveWalletDialog -> RemoveWalletDialogContent(dialog)
|
||||
is DialogModel.RenameWalletDialog -> RenameWalletDialogContent(dialog)
|
||||
is DialogModel.BiometricsLockoutDialog -> BiometricsLockoutDialogContent(dialog)
|
||||
is WarningModel.BiometricsLockoutWarning -> BiometricsLockoutWarningContent(dialog)
|
||||
is WarningModel.KeyInvalidatedWarning -> KeyInvalidatedWarningContent(dialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ package com.tangem.tap.features.walletSelector.ui
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.MyWallets
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.isLocked
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
|
||||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
|||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||
import com.tangem.tap.features.walletSelector.ui.model.MultiCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.SingleCurrencyUserWalletItem
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import com.tangem.tap.walletStoresManager
|
||||
|
|
@ -159,7 +160,7 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
}
|
||||
}
|
||||
}
|
||||
val biometricsLockoutDialog = createBiometricsLockoutDialogIfNeeded(state.error, prevState.dialog)
|
||||
val warningDialog = createWarningDialogIfNeeded(state.error, prevState.dialog)
|
||||
|
||||
prevState.copy(
|
||||
multiCurrencyWallets = multiCurrencyWallets,
|
||||
|
|
@ -169,9 +170,9 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
isLocked = state.isLocked,
|
||||
showUnlockProgress = state.isUnlockInProgress,
|
||||
showAddCardProgress = state.isCardSavingInProgress,
|
||||
dialog = biometricsLockoutDialog,
|
||||
dialog = warningDialog,
|
||||
error = state.error
|
||||
?.takeIf { !it.silent && biometricsLockoutDialog == null }
|
||||
?.takeIf { !it.silent && warningDialog == null }
|
||||
?.let { error ->
|
||||
error.messageResId?.let { TextReference.Res(it) }
|
||||
?: TextReference.Str(error.customMessage)
|
||||
|
|
@ -184,24 +185,23 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber<WalletSele
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
private fun createBiometricsLockoutDialogIfNeeded(
|
||||
private fun createWarningDialogIfNeeded(
|
||||
error: TangemError?,
|
||||
currentDialogModel: DialogModel?,
|
||||
currentDialog: DialogModel?,
|
||||
): DialogModel? {
|
||||
return when (error) {
|
||||
is TangemSdkError.BiometricsAuthenticationLockout -> DialogModel.BiometricsLockoutDialog(
|
||||
isPermanent = false,
|
||||
onDismiss = this::dismissDialog,
|
||||
is UserWalletsListError.BiometricsAuthenticationLockout -> WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = error.isPermanent,
|
||||
onDismiss = this::dismissWarningDialog,
|
||||
)
|
||||
is TangemSdkError.BiometricsAuthenticationPermanentLockout -> DialogModel.BiometricsLockoutDialog(
|
||||
isPermanent = true,
|
||||
onDismiss = this::dismissDialog,
|
||||
is UserWalletsListError.InvalidEncryptionKey -> WarningModel.KeyInvalidatedWarning(
|
||||
onDismiss = this::dismissWarningDialog,
|
||||
)
|
||||
else -> currentDialogModel
|
||||
else -> currentDialog
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissDialog() {
|
||||
private fun dismissWarningDialog() {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
dialog = null,
|
||||
|
|
|
|||
|
|
@ -8,26 +8,26 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.walletSelector.ui.model.DialogModel
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun BiometricsLockoutDialogContent(
|
||||
dialog: DialogModel.BiometricsLockoutDialog,
|
||||
internal fun BiometricsLockoutWarningContent(
|
||||
warning: WarningModel.BiometricsLockoutWarning,
|
||||
) {
|
||||
BasicDialog(
|
||||
title = stringResource(id = R.string.biometric_lockout_warning_title),
|
||||
message = stringResource(
|
||||
id = if (dialog.isPermanent) {
|
||||
id = if (warning.isPermanent) {
|
||||
R.string.biometric_lockout_permanent_warning_description
|
||||
} else {
|
||||
R.string.biometric_lockout_warning_description
|
||||
},
|
||||
),
|
||||
onDismissDialog = dialog.onDismiss,
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onDismiss,
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -38,8 +38,8 @@ private fun BiometricsLockoutDialogSample(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
BiometricsLockoutDialogContent(
|
||||
dialog = DialogModel.BiometricsLockoutDialog(
|
||||
BiometricsLockoutWarningContent(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = false,
|
||||
onDismiss = {},
|
||||
),
|
||||
|
|
@ -68,8 +68,8 @@ private fun BiometricsLockoutDialog_Permanent_Sample(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
BiometricsLockoutDialogContent(
|
||||
dialog = DialogModel.BiometricsLockoutDialog(
|
||||
BiometricsLockoutWarningContent(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = true,
|
||||
onDismiss = {},
|
||||
),
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.tangem.tap.features.walletSelector.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.walletSelector.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun KeyInvalidatedWarningContent(
|
||||
warning: WarningModel.KeyInvalidatedWarning,
|
||||
) {
|
||||
BasicDialog(
|
||||
title = stringResource(id = R.string.common_attention),
|
||||
message = stringResource(id = R.string.key_invalidated_warning_description),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
KeyInvalidatedWarningContent(
|
||||
warning = WarningModel.KeyInvalidatedWarning(onDismiss = {}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningPreview_Light() {
|
||||
TangemTheme {
|
||||
KeyInvalidatedWarningSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
KeyInvalidatedWarningSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -11,9 +11,15 @@ internal sealed interface DialogModel {
|
|||
val onConfirm: () -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
) : DialogModel
|
||||
}
|
||||
|
||||
data class BiometricsLockoutDialog(
|
||||
internal sealed interface WarningModel : DialogModel {
|
||||
data class BiometricsLockoutWarning(
|
||||
val isPermanent: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
) : DialogModel
|
||||
) : WarningModel
|
||||
|
||||
data class KeyInvalidatedWarning(
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import com.tangem.tap.tangemSdkManager
|
|||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
internal class WelcomeMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, appStateProvider ->
|
||||
|
|
@ -69,16 +70,15 @@ internal class WelcomeMiddleware {
|
|||
scope.launch {
|
||||
userWalletsListManager.unlockIfLockable()
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets with biometrics")
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
if (selectedUserWallet != null) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
}
|
||||
intentHandler.handleWalletConnectLink(state.intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,7 @@ internal class WelcomeMiddleware {
|
|||
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import com.tangem.core.ui.fragments.ComposeFragment
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.features.welcome.ui.components.BiometricsLockoutDialog
|
||||
import com.tangem.tap.features.welcome.ui.components.WarningDialog
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -49,7 +49,7 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val biometricsLockoutDialog by rememberUpdatedState(newValue = state.biometricsLockoutDialog)
|
||||
val warning by rememberUpdatedState(newValue = state.warning)
|
||||
|
||||
val backgroundColor = colorResource(id = R.color.background_primary)
|
||||
SystemBarsEffect {
|
||||
|
|
@ -80,7 +80,7 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
)
|
||||
}
|
||||
|
||||
BiometricsLockoutDialog(biometricsLockoutDialog)
|
||||
WarningDialog(warning)
|
||||
|
||||
LaunchedEffect(key1 = errorMessage) {
|
||||
errorMessage?.let {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package com.tangem.tap.features.welcome.ui
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.welcome.ui.model.BiometricsLockoutDialog
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
|
||||
@Immutable
|
||||
internal data class WelcomeScreenState(
|
||||
val showUnlockWithBiometricsProgress: Boolean = false,
|
||||
val showUnlockWithCardProgress: Boolean = false,
|
||||
val biometricsLockoutDialog: BiometricsLockoutDialog? = null,
|
||||
val warning: WarningModel? = null,
|
||||
val error: TextReference? = null,
|
||||
)
|
||||
|
|
@ -2,14 +2,14 @@ package com.tangem.tap.features.welcome.ui
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListError
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.features.welcome.ui.model.BiometricsLockoutDialog
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -41,15 +41,15 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
|
||||
// TODO: Refactor errors handling
|
||||
override fun newState(state: WelcomeState) {
|
||||
val biometricsLockoutDialog = createBiometricLockoutDialogIfNeeded(state.error)
|
||||
val warning = createWarningIfNeeded(state.error)
|
||||
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress,
|
||||
showUnlockWithCardProgress = state.isUnlockWithCardInProgress,
|
||||
biometricsLockoutDialog = biometricsLockoutDialog,
|
||||
warning = warning,
|
||||
error = state.error
|
||||
?.takeIf { !it.silent && biometricsLockoutDialog == null }
|
||||
?.takeIf { !it.silent && warning == null }
|
||||
?.let { e ->
|
||||
e.messageResId?.let { TextReference.Res(it) }
|
||||
?: TextReference.Str(e.customMessage)
|
||||
|
|
@ -62,24 +62,23 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
private fun createBiometricLockoutDialogIfNeeded(error: TangemError?): BiometricsLockoutDialog? {
|
||||
private fun createWarningIfNeeded(error: TangemError?): WarningModel? {
|
||||
return when (error) {
|
||||
is TangemSdkError.BiometricsAuthenticationLockout -> BiometricsLockoutDialog(
|
||||
isPermanent = false,
|
||||
onDismiss = this::dismissDialog,
|
||||
is UserWalletsListError.BiometricsAuthenticationLockout -> WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = error.isPermanent,
|
||||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
is TangemSdkError.BiometricsAuthenticationPermanentLockout -> BiometricsLockoutDialog(
|
||||
isPermanent = true,
|
||||
onDismiss = this::dismissDialog,
|
||||
is UserWalletsListError.InvalidEncryptionKey -> WarningModel.KeyInvalidatedWarning(
|
||||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissDialog() {
|
||||
private fun dismissWarning() {
|
||||
stateInternal.update { prevState ->
|
||||
prevState.copy(
|
||||
biometricsLockoutDialog = null,
|
||||
warning = null,
|
||||
)
|
||||
}
|
||||
closeError()
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.welcome.ui.model.BiometricsLockoutDialog
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun BiometricsLockoutDialog(
|
||||
dialog: BiometricsLockoutDialog?,
|
||||
) {
|
||||
if (dialog == null) return
|
||||
BasicDialog(
|
||||
title = stringResource(id = R.string.biometric_lockout_warning_title),
|
||||
message = stringResource(
|
||||
id = if (dialog.isPermanent) {
|
||||
R.string.biometric_lockout_permanent_warning_description
|
||||
} else {
|
||||
R.string.biometric_lockout_warning_description
|
||||
},
|
||||
),
|
||||
onDismissDialog = dialog.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
BiometricsLockoutDialog(
|
||||
dialog = BiometricsLockoutDialog(
|
||||
isPermanent = false,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogPreview_Light() {
|
||||
TangemTheme {
|
||||
BiometricsLockoutDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
BiometricsLockoutDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Sample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
BiometricsLockoutDialog(
|
||||
dialog = BiometricsLockoutDialog(
|
||||
isPermanent = true,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Preview_Light() {
|
||||
TangemTheme {
|
||||
BiometricsLockoutDialog_Permanent_Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Preview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
BiometricsLockoutDialog_Permanent_Sample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package com.tangem.tap.features.welcome.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun WarningDialog(
|
||||
warning: WarningModel?,
|
||||
) {
|
||||
when (warning) {
|
||||
null -> Unit
|
||||
is WarningModel.BiometricsLockoutWarning -> {
|
||||
BasicDialog(
|
||||
title = stringResource(id = R.string.biometric_lockout_warning_title),
|
||||
message = stringResource(
|
||||
id = if (warning.isPermanent) {
|
||||
R.string.biometric_lockout_permanent_warning_description
|
||||
} else {
|
||||
R.string.biometric_lockout_warning_description
|
||||
},
|
||||
),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WarningModel.KeyInvalidatedWarning -> {
|
||||
BasicDialog(
|
||||
title = stringResource(id = R.string.common_attention),
|
||||
message = stringResource(id = R.string.key_invalidated_warning_description),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = false,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogPreview_Light() {
|
||||
TangemTheme {
|
||||
BiometricsLockoutDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
BiometricsLockoutDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Sample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = true,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Preview_Light() {
|
||||
TangemTheme {
|
||||
BiometricsLockoutDialog_Permanent_Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Preview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
BiometricsLockoutDialog_Permanent_Sample()
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningSample(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(warning = WarningModel.KeyInvalidatedWarning(onDismiss = {}))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningPreview_Light() {
|
||||
TangemTheme {
|
||||
KeyInvalidatedWarningSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
KeyInvalidatedWarningSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
// endregion Preview
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.model
|
||||
|
||||
internal data class BiometricsLockoutDialog(
|
||||
val isPermanent: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tap.features.welcome.ui.model
|
||||
|
||||
internal sealed interface WarningModel {
|
||||
data class BiometricsLockoutWarning(
|
||||
val isPermanent: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
|
||||
data class KeyInvalidatedWarning(
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
}
|
||||
|
|
@ -28,7 +28,11 @@ class UserWalletManagerImpl(
|
|||
private val appStateHolder: AppStateHolder,
|
||||
) : UserWalletManager {
|
||||
|
||||
override suspend fun getUserTokens(networkId: String, isExcludeCustom: Boolean): List<Currency> {
|
||||
override suspend fun getUserTokens(
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
isExcludeCustom: Boolean,
|
||||
): List<Currency> {
|
||||
val card = appStateHolder.getActualCard()
|
||||
val userTokensRepository =
|
||||
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
|
||||
|
|
@ -40,7 +44,9 @@ class UserWalletManagerImpl(
|
|||
} else {
|
||||
true
|
||||
}
|
||||
it.blockchain.toNetworkId() == networkId && checkCustom
|
||||
it.blockchain.toNetworkId() == networkId &&
|
||||
checkCustom &&
|
||||
it.derivationPath == derivationPath
|
||||
}
|
||||
.map {
|
||||
if (it is com.tangem.tap.features.wallet.models.Currency.Token) {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ object Versions {
|
|||
const val tangemBlockchainSdk = "develop-174"
|
||||
// const val tangemBlockchainSdk = "0.0.1" // Keep it! - used for local builds
|
||||
|
||||
const val tangemCardSdk = "develop-199"
|
||||
const val tangemCardSdk = "develop-200"
|
||||
// const val tangemCardSdk = "0.0.1" // Keep it! - used for local builds
|
||||
// endregion Tangem
|
||||
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@
|
|||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress…</string>
|
||||
<string name="swapping_pending_transaction_title">Waiting</string>
|
||||
<string name="swapping_permission_buttons_approve">Approve</string>
|
||||
<string name="swapping_permission_header">Give Permission</string>
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress...</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
<string name="twins_onboarding_description_format">This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.</string>
|
||||
<string name="twins_onboarding_subtitle">One wallet. Two cards.</string>
|
||||
|
|
@ -384,6 +384,7 @@
|
|||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
|
||||
<string name="user_wallet_list_multi_header">Multi-currency</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@
|
|||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress…</string>
|
||||
<string name="swapping_pending_transaction_title">Waiting</string>
|
||||
<string name="swapping_permission_buttons_approve">Approve</string>
|
||||
<string name="swapping_permission_header">Give Permission</string>
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress...</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
<string name="twins_onboarding_description_format">This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.</string>
|
||||
<string name="twins_onboarding_subtitle">One wallet. Two cards.</string>
|
||||
|
|
@ -384,6 +384,7 @@
|
|||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
|
||||
<string name="user_wallet_list_multi_header">Multi-currency</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@
|
|||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">Insufficient funds</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">Not enough funds for fee in your %1$s wallet to create a transaction. Top up your %2$s wallet first.</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress...</string>
|
||||
<string name="swapping_pending_transaction_subtitle">Transaction in progress…</string>
|
||||
<string name="swapping_pending_transaction_title">Waiting</string>
|
||||
<string name="swapping_permission_buttons_approve">Approve</string>
|
||||
<string name="swapping_permission_header">Give Permission</string>
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress...</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
<string name="twins_onboarding_description_format">This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.</string>
|
||||
<string name="twins_onboarding_subtitle">One wallet. Two cards.</string>
|
||||
|
|
@ -384,6 +384,7 @@
|
|||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
|
||||
<string name="user_wallet_list_multi_header">Multi-currency</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
<string name="initial_message_sign_header">Нажмите, чтобы подписать</string>
|
||||
<string name="initial_message_tap_header">Приложите карту</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
|
||||
<string name="key_invalidated_warning_description">Вы обновили данные биометрии, отсканируйте свою карту для входа</string>
|
||||
<string name="main_manage_tokens">Управление токенами</string>
|
||||
<string name="main_no_backup_warning_subtitle">Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру</string>
|
||||
<string name="main_no_backup_warning_title">Бэкап кошелька не был произведен</string>
|
||||
|
|
@ -384,6 +385,7 @@
|
|||
<string name="user_wallet_list_add_button">Добавить новый кошелек</string>
|
||||
<string name="user_wallet_list_delete_prompt">Вы уверены, что хотите удалить этот кошелек?</string>
|
||||
<string name="user_wallet_list_editing_count">%d выбрано</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">Произошла ошибка, пожалуйста, отсканируйте свою карту для входа</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">Этот кошелек уже был сохранен, вы можете добавить другой</string>
|
||||
<string name="user_wallet_list_multi_header">Мультивалютные</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Имя кошелька</string>
|
||||
|
|
@ -418,6 +420,7 @@
|
|||
<string name="wallet_connect_error_timeout">Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже.</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации.</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">Неверная карта выбрана в приложении Tangem</string>
|
||||
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_network_not_found_format">Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.</string>
|
||||
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@
|
|||
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
|
||||
<string name="swapping_insufficient_funds">餘額不足</string>
|
||||
<string name="swapping_not_enough_funds_for_fee">您的 %1$s 錢包中沒有足夠的資金來創建交易。首先為您的 %2$s 錢包充值</string>
|
||||
<string name="swapping_pending_transaction_subtitle">交易進行中...</string>
|
||||
<string name="swapping_pending_transaction_subtitle">交易進行中…</string>
|
||||
<string name="swapping_pending_transaction_title">等待中</string>
|
||||
<string name="swapping_permission_buttons_approve">允許</string>
|
||||
<string name="swapping_permission_header">賦予權限</string>
|
||||
|
|
@ -371,7 +371,7 @@
|
|||
<string name="transaction_history_empty_transactions">您還沒有任何交易</string>
|
||||
<string name="transaction_history_error_failed_to_load">無法加載交易</string>
|
||||
<string name="transaction_history_title">交易</string>
|
||||
<string name="transaction_history_tx_in_progress">進行中...</string>
|
||||
<string name="transaction_history_tx_in_progress">進行中…</string>
|
||||
<string name="twin_error_same_card">您掃描了同一張卡片。要創建雙錢包,您需要掃描編號為 %d 的卡</string>
|
||||
<string name="twins_onboarding_description_format">這一個是你手裡拿著的,另一個是編號為 %s 的,這兩張卡都可以用來從這個錢包中提取資金</string>
|
||||
<string name="twins_onboarding_subtitle">一個錢包,兩張卡片</string>
|
||||
|
|
@ -384,6 +384,7 @@
|
|||
<string name="user_wallet_list_add_button">添加新錢包</string>
|
||||
<string name="user_wallet_list_delete_prompt">您確定要刪除此錢包?</string>
|
||||
<string name="user_wallet_list_editing_count">已選擇 %d</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">此錢包已保存,您可以再添加一個</string>
|
||||
<string name="user_wallet_list_multi_header">多幣種</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">錢包名稱</string>
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@
|
|||
<string name="initial_message_sign_header">Tap to sign</string>
|
||||
<string name="initial_message_tap_header">Tap the card</string>
|
||||
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
|
||||
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
|
||||
<string name="main_manage_tokens">Manage tokens</string>
|
||||
<string name="main_no_backup_warning_subtitle">To protect your assets, we advise you to carry out this procedure</string>
|
||||
<string name="main_no_backup_warning_title">Your wallet has not been backed up</string>
|
||||
|
|
@ -371,7 +372,7 @@
|
|||
<string name="transaction_history_empty_transactions">You don\'t have any transactions yet</string>
|
||||
<string name="transaction_history_error_failed_to_load">Failed to load transactions</string>
|
||||
<string name="transaction_history_title">Transactions</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress...</string>
|
||||
<string name="transaction_history_tx_in_progress">In progress…</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
<string name="twins_onboarding_description_format">This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.</string>
|
||||
<string name="twins_onboarding_subtitle">One wallet. Two cards.</string>
|
||||
|
|
@ -384,6 +385,7 @@
|
|||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_editing_count">%d selected</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
<string name="user_wallet_list_error_wallet_already_saved">This wallet has already been saved, you can add another one</string>
|
||||
<string name="user_wallet_list_multi_header">Multi-currency</string>
|
||||
<string name="user_wallet_list_rename_popup_placeholder">Wallet name</string>
|
||||
|
|
@ -418,6 +420,7 @@
|
|||
<string name="wallet_connect_error_timeout">Failed to establish WalletConnect session: timeout error. Please, try again later.</string>
|
||||
<string name="wallet_connect_error_unsupported_blockchains">Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n</string>
|
||||
<string name="wallet_connect_error_unsupported_dapp">Connection with this Dapp cannot be established due to its technical implementation.</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">Wrong card selected in Tangem App</string>
|
||||
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
|
||||
<string name="wallet_connect_network_not_found_format">%s network not found. Please, add it first and try again.</string>
|
||||
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
|
||||
|
|
|
|||
|
|
@ -94,9 +94,10 @@ interface SwapInteractor {
|
|||
/**
|
||||
* Returns token in wallet balance
|
||||
*
|
||||
* @param networkId
|
||||
* @param token
|
||||
*/
|
||||
fun getTokenBalance(token: Currency): SwapAmount
|
||||
fun getTokenBalance(networkId: String, token: Currency): SwapAmount
|
||||
|
||||
fun isAvailableToSwap(networkId: String): Boolean
|
||||
}
|
||||
|
|
@ -56,14 +56,18 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
|
||||
// replace tokens in wallet tokens list with loaded same
|
||||
val loadedOnWalletsMap = mutableSetOf<String>()
|
||||
val tokensInWallet = userWalletManager.getUserTokens(networkId = networkId, isExcludeCustom = true)
|
||||
.filter { it.symbol != initialCurrency.symbol }
|
||||
.map { token ->
|
||||
allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let {
|
||||
loadedOnWalletsMap.add(it.symbol)
|
||||
it
|
||||
} ?: cryptoCurrencyConverter.convertBack(token)
|
||||
}
|
||||
val tokensInWallet =
|
||||
userWalletManager.getUserTokens(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
isExcludeCustom = true,
|
||||
).filter { it.symbol != initialCurrency.symbol }
|
||||
.map { token ->
|
||||
allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let {
|
||||
loadedOnWalletsMap.add(it.symbol)
|
||||
it
|
||||
} ?: cryptoCurrencyConverter.convertBack(token)
|
||||
}
|
||||
val loadedTokens = allLoadedTokens
|
||||
.filter {
|
||||
!loadedOnWalletsMap.contains(it.symbol)
|
||||
|
|
@ -72,7 +76,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
.mapValues { SwapAmount(it.value.value, it.value.decimals) }
|
||||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id })
|
||||
cache.cacheBalances(tokensBalance)
|
||||
cache.cacheBalances(networkId, derivationPath, tokensBalance)
|
||||
cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) })
|
||||
cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency))
|
||||
return TokensDataState(
|
||||
|
|
@ -147,7 +151,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
syncWalletBalanceForTokens(networkId, listOf(fromToken, toToken))
|
||||
val amountDecimal = toBigDecimalOrNull(amountToSwap)
|
||||
if (amountDecimal == null || amountDecimal.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return createEmptyAmountState(fromToken, toToken)
|
||||
return createEmptyAmountState(networkId, fromToken, toToken)
|
||||
}
|
||||
val amount = SwapAmount(amountDecimal, getTokenDecimals(fromToken))
|
||||
val fromTokenAddress = getTokenAddress(fromToken)
|
||||
|
|
@ -157,7 +161,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
allowPermissionsHandler.removeAddressFromProgress(fromTokenAddress)
|
||||
transactionManager.updateWalletManager(networkId, derivationPath)
|
||||
}
|
||||
val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null)
|
||||
val isBalanceWithoutFeeEnough = isBalanceEnough(networkId, fromToken, amount, null)
|
||||
return if (isAllowedToSpend && isBalanceWithoutFeeEnough) {
|
||||
loadSwapData(
|
||||
networkId = networkId,
|
||||
|
|
@ -224,8 +228,12 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getTokenBalance(token: Currency): SwapAmount {
|
||||
return cache.getBalanceForToken(token.symbol) ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token))
|
||||
override fun getTokenBalance(networkId: String, token: Currency): SwapAmount {
|
||||
return cache.getBalanceForToken(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
symbol = token.symbol,
|
||||
) ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token))
|
||||
}
|
||||
|
||||
override fun isAvailableToSwap(networkId: String): Boolean {
|
||||
|
|
@ -298,12 +306,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createEmptyAmountState(
|
||||
networkId: String,
|
||||
fromToken: Currency,
|
||||
toToken: Currency,
|
||||
): SwapState {
|
||||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val fromTokenBalance = cache.getBalanceForToken(fromToken.symbol)
|
||||
val toTokenBalance = cache.getBalanceForToken(toToken.symbol)
|
||||
val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol)
|
||||
val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol)
|
||||
return SwapState.EmptyAmountState(
|
||||
fromTokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(),
|
||||
toTokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }.orEmpty(),
|
||||
|
|
@ -411,7 +420,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
decimals = transactionManager.getNativeTokenDecimals(networkId),
|
||||
currency = userWalletManager.getNetworkCurrency(networkId),
|
||||
) + feeFiat
|
||||
val isBalanceIncludeFeeEnough = isBalanceEnough(fromToken, amount, feeData.fee.value)
|
||||
val isBalanceIncludeFeeEnough = isBalanceEnough(networkId, fromToken, amount, feeData.fee.value)
|
||||
val isFeeEnough = checkFeeIsEnough(
|
||||
fee = feeData.fee.value,
|
||||
spendAmount = amount,
|
||||
|
|
@ -458,8 +467,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val appCurrency = userWalletManager.getUserAppCurrency()
|
||||
val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId)
|
||||
val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id, nativeToken.id))
|
||||
val fromTokenBalance = cache.getBalanceForToken(fromToken.symbol)
|
||||
val toTokenBalance = cache.getBalanceForToken(toToken.symbol)
|
||||
val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol)
|
||||
val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol)
|
||||
return SwapState.QuotesLoadedState(
|
||||
fromTokenInfo = TokenSwapInfo(
|
||||
tokenAmount = fromTokenAmount,
|
||||
|
|
@ -503,7 +512,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
quotesLoadedState: SwapState.QuotesLoadedState,
|
||||
): SwapState.QuotesLoadedState {
|
||||
// if token balance ZERO not show permission state to avoid user to spend money for fee
|
||||
val isTokenZeroBalance = getTokenBalance(fromToken).value.compareTo(BigDecimal.ZERO) == 0
|
||||
val isTokenZeroBalance = getTokenBalance(networkId, fromToken).value.compareTo(BigDecimal.ZERO) == 0
|
||||
if (isTokenZeroBalance) {
|
||||
return quotesLoadedState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
|
|
@ -546,7 +555,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List<Currency>) {
|
||||
val tokensToSync = tokens.filter { cache.getBalanceForToken(it.symbol) == null }
|
||||
val tokensToSync = tokens.filter { cache.getBalanceForToken(networkId, derivationPath, it.symbol) == null }
|
||||
if (tokensToSync.isNotEmpty()) {
|
||||
val tokensBalance =
|
||||
userWalletManager.getCurrentWalletTokensBalance(
|
||||
|
|
@ -554,12 +563,21 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
extraTokens = tokensToSync.map { cryptoCurrencyConverter.convert(it) },
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
cache.cacheBalances(tokensBalance.mapValues { SwapAmount(it.value.value, it.value.decimals) })
|
||||
cache.cacheBalances(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
balances = tokensBalance.mapValues { SwapAmount(it.value.value, it.value.decimals) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBalanceEnough(fromToken: Currency, amount: SwapAmount, fee: BigDecimal?): Boolean {
|
||||
val tokenBalance = getTokenBalance(fromToken).value
|
||||
private fun isBalanceEnough(
|
||||
networkId: String,
|
||||
fromToken: Currency,
|
||||
amount: SwapAmount,
|
||||
fee: BigDecimal?,
|
||||
): Boolean {
|
||||
val tokenBalance = getTokenBalance(networkId, fromToken).value
|
||||
return if (fromToken is Currency.NonNativeToken) {
|
||||
tokenBalance >= amount.value
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ interface SwapDataCache {
|
|||
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
|
||||
fun cacheInWalletTokens(tokens: List<TokenWithBalance>)
|
||||
fun cacheLoadedTokens(tokens: List<TokenWithBalance>)
|
||||
fun cacheBalances(balances: Map<String, SwapAmount>)
|
||||
fun cacheBalances(networkId: String, derivationPath: String?, balances: Map<String, SwapAmount>)
|
||||
fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String)
|
||||
fun getAvailableTokens(networkId: String): List<Currency>
|
||||
fun getInWalletTokens(): List<TokenWithBalance>
|
||||
fun getLoadedTokens(): List<TokenWithBalance>
|
||||
fun getBalanceForToken(symbol: String): SwapAmount?
|
||||
fun getBalanceForToken(networkId: String, derivationPath: String?, symbol: String): SwapAmount?
|
||||
fun getLastFeeForNetwork(networkId: String): BigDecimal?
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
|
||||
private val availableTokensForNetwork: MutableMap<String, List<Currency>> = mutableMapOf()
|
||||
private val feesForNetworks: MutableMap<String, BigDecimal> = mutableMapOf()
|
||||
private val tokensBalances: MutableMap<String, SwapAmount> = mutableMapOf()
|
||||
private val tokensBalances: MutableMap<String, Map<String, SwapAmount>> = mutableMapOf()
|
||||
private val lastInWalletTokens = mutableListOf<TokenWithBalance>()
|
||||
private val lastLoadedTokens = mutableListOf<TokenWithBalance>()
|
||||
|
||||
|
|
@ -35,12 +35,12 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
return lastLoadedTokens
|
||||
}
|
||||
|
||||
override fun getBalanceForToken(symbol: String): SwapAmount? {
|
||||
return tokensBalances[symbol]
|
||||
override fun getBalanceForToken(networkId: String, derivationPath: String?, symbol: String): SwapAmount? {
|
||||
return tokensBalances[createKeyFrom(networkId, derivationPath)]?.get(symbol)
|
||||
}
|
||||
|
||||
override fun cacheBalances(balances: Map<String, SwapAmount>) {
|
||||
tokensBalances.putAll(balances)
|
||||
override fun cacheBalances(networkId: String, derivationPath: String?, balances: Map<String, SwapAmount>) {
|
||||
tokensBalances[createKeyFrom(networkId, derivationPath)] = balances
|
||||
}
|
||||
|
||||
override fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>) {
|
||||
|
|
@ -54,4 +54,8 @@ class SwapDataCacheImpl : SwapDataCache {
|
|||
override fun getAvailableTokens(networkId: String): List<Currency> {
|
||||
return availableTokensForNetwork.getOrElse(networkId) { emptyList() }
|
||||
}
|
||||
|
||||
private fun createKeyFrom(networkId: String, derivationPath: String?): String {
|
||||
return "$networkId;$derivationPath"
|
||||
}
|
||||
}
|
||||
|
|
@ -371,7 +371,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
private fun onMaxAmountClicked() {
|
||||
dataState.fromCurrency?.let {
|
||||
val balance = swapInteractor.getTokenBalance(it)
|
||||
val balance = swapInteractor.getTokenBalance(currency.networkId, it)
|
||||
onAmountChanged(balance.formatToUIRepresentation())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ interface UserWalletManager {
|
|||
* Returns all user tokens (merged from local and backend)
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getUserTokens(networkId: String, isExcludeCustom: Boolean): List<Currency>
|
||||
suspend fun getUserTokens(networkId: String, derivationPath: String?, isExcludeCustom: Boolean): List<Currency>
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getNativeTokenForNetwork(networkId: String): Currency
|
||||
|
|
@ -55,6 +55,7 @@ interface UserWalletManager {
|
|||
* Return balances from wallet found by networkId
|
||||
*
|
||||
* @param networkId
|
||||
* @param extraTokens tokens you want to check balance that not exists in wallet
|
||||
* @param derivationPath if null uses default
|
||||
* @return map of <Symbol, [ProxyAmount]>
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue