diff --git a/core/ui/src/main/java/com/tangem/core/ui/security/DisableAutofill.kt b/core/ui/src/main/java/com/tangem/core/ui/security/DisableAutofill.kt new file mode 100644 index 0000000000..8693a0ea3e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/security/DisableAutofill.kt @@ -0,0 +1,31 @@ +package com.tangem.core.ui.security + +import android.os.Build +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalView + +/** + * Disables autofill for the current Compose view hierarchy. + * + * This prevents password managers and other autofill services from accessing + * sensitive content (e.g., seed phrases, private keys) entered in text fields. + * + * Must be called within a Composable scope before the text fields that need protection. + * + * The original autofill setting is restored when this composable leaves the composition. + */ +@Composable +fun DisableAutofillEffect() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val view = LocalView.current + DisposableEffect(view) { + val previousValue = view.importantForAutofill + view.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS + onDispose { + view.importantForAutofill = previousValue + } + } + } +} \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt index 2bef64e9b1..2774fa49fe 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt @@ -84,7 +84,7 @@ class ManageCryptoCurrenciesUseCase( withContext(NonCancellable) { val accountStatus = getAccountStatus(accountId = accountId) - val modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies() + var modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies() .modify(add = add, remove = remove) if (!modifiedCurrencyList.hasChanges) { @@ -92,12 +92,21 @@ class ManageCryptoCurrenciesUseCase( return@withContext } + val derivingResult = derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) + + if (!skipDerivationErrors && derivingResult.isLeft()) { + modifiedCurrencyList = accountStatus.tokenList.flattenCurrencies() + .modify(add = emptyList(), remove = remove) + + if (!modifiedCurrencyList.hasChanges) { + derivingResult.bind() + } + } + saveAccount( account = accountStatus.account.copy(cryptoCurrencies = modifiedCurrencyList.total), ) - val result = derivePublicKeys(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) - parallelUpdatingScope.launch { syncTokens(userWalletId, modifiedCurrencyList) @@ -107,7 +116,7 @@ class ManageCryptoCurrenciesUseCase( } if (!skipDerivationErrors) { - result.bind() + derivingResult.bind() } } } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index ea9d7eaa60..5418e2b7b1 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.managetokens.model import arrow.core.getOrElse +import com.tangem.common.core.TangemSdkError import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -8,6 +9,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.message.DialogMessage import com.tangem.domain.managetokens.CreateCryptoCurrencyUseCase import com.tangem.domain.managetokens.FindTokenUseCase @@ -146,7 +148,7 @@ internal class CustomTokenFormModel @Inject constructor( is CustomCurrencyValidator.Status.Validating, -> Unit is CustomCurrencyValidator.Status.SearchingToken -> updateStateWithProgress() - is CustomCurrencyValidator.Status.UnexpectedException -> showErrorDialog() + is CustomCurrencyValidator.Status.UnexpectedException -> showErrorDialog(validatorState.cause) is CustomCurrencyValidator.Status.FormValidationException -> updateStateWithExceptions( exceptions = validatorState.exceptions, ) @@ -236,9 +238,17 @@ internal class CustomTokenFormModel @Inject constructor( } } - private fun showErrorDialog() { + private fun showErrorDialog(throwable: Throwable) { + Timber.e(throwable) + val message = when (throwable) { + is TangemSdkError -> resourceReference( + R.string.generic_error_code, + wrappedList(throwable.code.toString()), + ) + else -> resourceReference(R.string.common_unknown_error) + } val dialog = DialogMessage( - message = resourceReference(R.string.common_unknown_error), + message = message, ) messageSender.send(dialog) @@ -341,8 +351,12 @@ internal class CustomTokenFormModel @Inject constructor( ) { val currency = createdCurrency if (currency == null) { - Timber.e("Trying to add currency without validation") - showErrorDialog() + showErrorDialog(IllegalStateException("Trying to add currency without validation")) + return@resource + } + + useCasesFacade.addCryptoCurrenciesUseCase(currency).getOrElse { throwable -> + showErrorDialog(throwable) return@resource } @@ -353,12 +367,6 @@ internal class CustomTokenFormModel @Inject constructor( ) analyticsEventHandler.send(event) - useCasesFacade.addCryptoCurrenciesUseCase(currency).getOrElse { throwable -> - Timber.e(throwable, "Failed to add currency") - showErrorDialog() - return@resource - } - params.onCurrencyAdded(currency) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt index 084931f1a7..ea253fd2d2 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/CustomTokenFormUseCasesFacade.kt @@ -34,7 +34,11 @@ internal class CustomTokenFormUseCasesFacade @AssistedInject constructor( return either { val accountId = getAccountId(currency) - manageCryptoCurrenciesUseCase(accountId = accountId, add = currency).bind() + manageCryptoCurrenciesUseCase( + accountId = accountId, + add = currency, + skipDerivationErrors = false, + ).bind() } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt index 8779ab378a..a5fda3ab83 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt @@ -28,6 +28,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.security.DisableAutofillEffect import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils.InvalidWordsColorTransformation @@ -53,6 +54,8 @@ internal fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, ) } + DisableAutofillEffect() + Box(modifier.fillMaxSize()) { Column( modifier = Modifier