diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt index f8361ffae1..f316b2e433 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSigner.kt @@ -5,6 +5,7 @@ import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.Wallet import com.tangem.common.CompletionResult +import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.domain.tasks.SignHashesTask import kotlinx.coroutines.suspendCancellableCoroutine @@ -23,12 +24,12 @@ class TangemSigner( publicKey: Wallet.PublicKey, ): CompletionResult> { return suspendCancellableCoroutine { continuation -> - val cardId = if (card.backupStatus?.isActive == true) null else card.cardId - + val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins val task = SignHashesTask(hashes, publicKey) + tangemSdk.startSessionWithRunnable( runnable = task, - cardId = cardId, + cardId = card.cardId.takeIf { isCardNotBackedUp }, initialMessage = initialMessage, accessCode = accessCode, ) { result -> diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListError.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListError.kt index c73891df83..ec9ed95b1d 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListError.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListError.kt @@ -4,6 +4,7 @@ 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 @@ -18,6 +19,10 @@ sealed class UserWalletsListError(code: Int) : TangemError(code) { override var customMessage: String = "Encryption key invalidated" } + object BiometricsAuthenticationDisabled : UserWalletsListError(code = 60005) { + override var customMessage: String = "Biometrics authentication disabled" + } + data class BiometricsAuthenticationLockout(val isPermanent: Boolean) : UserWalletsListError(code = 60003) { override var customMessage: String = "Biometric authentication lockout, permanent: $isPermanent" } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt index ec4c83c460..71255de6a0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/di/UserWalletsListManagerProvider.kt @@ -17,6 +17,7 @@ import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUse import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository import com.tangem.tap.domain.userWalletList.utils.json.ByteArrayKeyAdapter import com.tangem.tap.domain.userWalletList.utils.json.CardBackupStatusAdapter +import com.tangem.tap.domain.userWalletList.utils.json.DerivationPathAdapterWithMigration import com.tangem.tap.domain.userWalletList.utils.json.ExtendedPublicKeysMapAdapter import com.tangem.tap.domain.userWalletList.utils.json.ScanResponseDerivedKeysMapAdapter import com.tangem.tap.domain.userWalletList.utils.json.WalletDerivedKeysMapAdapter @@ -33,8 +34,8 @@ fun UserWalletsListManager.Companion.provideBiometricImplementation( .add(ByteArrayKeyAdapter()) .add(ExtendedPublicKeysMapAdapter()) .add(CardBackupStatusAdapter()) + .add(DerivationPathAdapterWithMigration()) .add(TangemSdkAdapter.DateAdapter()) - .add(TangemSdkAdapter.DerivationPathAdapter()) .add(TangemSdkAdapter.DerivationNodeAdapter()) .add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model .add(KotlinJsonAdapterFactory()) diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt index 5d39710ca7..affeeeef57 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/BiometricUserWalletsKeysRepository.kt @@ -48,6 +48,8 @@ internal class BiometricUserWalletsKeysRepository( UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true) is TangemSdkError.BiometricCryptographyKeyInvalidated -> UserWalletsListError.EncryptionKeyInvalidated + is TangemSdkError.BiometricsUnavailable -> + UserWalletsListError.BiometricsAuthenticationDisabled else -> error } } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/DerivationPathAdapterWithMigration.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/DerivationPathAdapterWithMigration.kt new file mode 100644 index 0000000000..4719b96b49 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/json/DerivationPathAdapterWithMigration.kt @@ -0,0 +1,35 @@ +package com.tangem.tap.domain.userWalletList.utils.json + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import com.tangem.common.extensions.guard +import com.tangem.common.json.MoshiJsonConverter +import com.tangem.crypto.hdWallet.DerivationNode +import com.tangem.crypto.hdWallet.DerivationPath +import timber.log.Timber + +class DerivationPathAdapterWithMigration { + @ToJson + fun toJson(src: DerivationPath): String = src.rawPath + + @FromJson + fun fromJson(json: String): DerivationPath { + val jsonMap = MoshiJsonConverter.default().toMap(json) + return if (jsonMap.isEmpty()) { + DerivationPath(json) + } else { + fromLegacyScheme(jsonMap) + } + } + + @Suppress("UNCHECKED_CAST") + private fun fromLegacyScheme(jsonMap: Map): DerivationPath { + val rawPath = jsonMap["rawPath"] as String + val nodeIndexes = (jsonMap["nodes"] as? List).guard { + Timber.e("Unable to convert derivation path nodes from JSON") + return DerivationPath(rawPath) + } + val nodes = nodeIndexes.map { DerivationNode.fromIndex(it.toLong()) } + return DerivationPath(rawPath, nodes) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt index d4526edf76..f2fd8a3b07 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreen.kt @@ -160,7 +160,10 @@ fun DetailsItem(item: SettingsElement, appCurrency: String, onItemsClick: () -> @Composable fun TangemSocialAccounts(links: List, onSocialNetworkClick: (SocialNetworkLink) -> Unit) { - LazyRow(modifier = Modifier.padding(start = 8.dp, end = 8.dp)) { + LazyRow( + modifier = Modifier.padding(start = 8.dp, end = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { items(links) { Icon( painter = painterResource(id = it.network.iconRes), diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt index ce8e1fd1ef..c0dd629819 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/details/DetailsScreenState.kt @@ -55,25 +55,28 @@ sealed class SocialNetwork(val id: String, val iconRes: Int) { object GitHub : SocialNetwork("GitHub", R.drawable.ic_github) object YouTube : SocialNetwork("YouTube", R.drawable.ic_youtube) object LinkedIn : SocialNetwork("LinkedIn", R.drawable.ic_linkedin) + object Discord : SocialNetwork("Discord", R.drawable.ic_discord) } object TangemSocialAccounts { val accountsEn: List = listOf( - SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/TangemCards"), + SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat"), SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"), SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"), SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"), SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"), SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"), + SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/7AqTVyqdGS"), ) val accountsRu: List = listOf( - SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_ru"), + SocialNetworkLink(SocialNetwork.Telegram, "https://t.me/tangem_chat_ru"), SocialNetworkLink(SocialNetwork.Twitter, "https://twitter.com/tangem"), SocialNetworkLink(SocialNetwork.Facebook, "https://m.facebook.com/TangemCards/"), SocialNetworkLink(SocialNetwork.Instagram, "https://instagram.com/tangemcards"), SocialNetworkLink(SocialNetwork.GitHub, "https://github.com/tangem"), SocialNetworkLink(SocialNetwork.YouTube, "https://youtube.com/channel/UCFGwLS7yggzVkP6ozte0m1w"), SocialNetworkLink(SocialNetwork.LinkedIn, "https://www.linkedin.com/company/tangem"), + SocialNetworkLink(SocialNetwork.Discord, "https://discord.gg/7AqTVyqdGS"), ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt index 9f82671d66..4f24d56193 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt @@ -25,6 +25,7 @@ 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.BiometricsDisabledWarningContent 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 @@ -96,6 +97,7 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment RenameWalletDialogContent(dialog) is WarningModel.BiometricsLockoutWarning -> BiometricsLockoutWarningContent(dialog) is WarningModel.KeyInvalidatedWarning -> KeyInvalidatedWarningContent(dialog) + is WarningModel.BiometricsDisabledWarning -> BiometricsDisabledWarningContent(dialog) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt index 2637e44e3b..99024dad35 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt @@ -199,6 +199,10 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber WarningModel.KeyInvalidatedWarning( onDismiss = this::dismissWarningDialog, ) + is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning( + onConfirm = { /* [REDACTED_TODO_COMMENT] */ }, + onDismiss = this::dismissWarningDialog, + ) else -> currentDialog } } diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt new file mode 100644 index 0000000000..429ab5471e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/BiometricsDisabledWarningContent.kt @@ -0,0 +1,55 @@ +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 BiometricsDisabledWarningContent(warning: WarningModel.BiometricsDisabledWarning) { + BasicDialog( + title = stringResource(id = R.string.common_warning), + message = stringResource(id = R.string.biometric_unavailable_warning), + onDismissDialog = warning.onDismiss, + confirmButton = DialogButton( + title = stringResource(id = R.string.common_ok), + onClick = warning.onDismiss, + ), + ) +} + +// region Preview +@Composable +private fun BiometricsDisabledWarningContentSample(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + BiometricsDisabledWarningContent( + warning = WarningModel.BiometricsDisabledWarning( + onConfirm = {}, + onDismiss = {}, + ), + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun BiometricsDisabledWarningContentPreview_Light() { + TangemTheme { + BiometricsDisabledWarningContentSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun BiometricsDisabledWarningContentPreview_Dark() { + TangemTheme(isDark = true) { + BiometricsDisabledWarningContentSample() + } +} +// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt index 8024e00021..b458313c35 100644 --- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt +++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/model/DialogModel.kt @@ -22,4 +22,9 @@ internal sealed interface WarningModel : DialogModel { data class KeyInvalidatedWarning( val onDismiss: () -> Unit, ) : WarningModel + + data class BiometricsDisabledWarning( + val onConfirm: () -> Unit, + val onDismiss: () -> Unit, + ) : WarningModel } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt index 3687df607c..e19891caa2 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt @@ -71,6 +71,10 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber { is UserWalletsListError.EncryptionKeyInvalidated -> WarningModel.KeyInvalidatedWarning( onDismiss = this::dismissWarning, ) + is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning( + onConfirm = { /* [REDACTED_TODO_COMMENT] */ }, + onDismiss = this::dismissWarning, + ) else -> null } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt index 40ae13b0cb..d8a78e246f 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WarningDialog.kt @@ -43,6 +43,17 @@ internal fun WarningDialog(warning: WarningModel?) { ), ) } + is WarningModel.BiometricsDisabledWarning -> { + BasicDialog( + title = stringResource(id = R.string.common_warning), + message = stringResource(id = R.string.biometric_unavailable_warning), + onDismissDialog = warning.onDismiss, + confirmButton = DialogButton( + title = stringResource(id = R.string.common_ok), + onClick = warning.onConfirm, + ), + ) + } } } @@ -103,7 +114,6 @@ private fun BiometricsLockoutDialog_Permanent_Preview_Dark() { } } -// region Preview @Composable private fun KeyInvalidatedWarningSample(modifier: Modifier = Modifier) { Column(modifier = modifier) { @@ -126,5 +136,27 @@ private fun KeyInvalidatedWarningPreview_Dark() { KeyInvalidatedWarningSample() } } -// endregion Preview + +@Composable +private fun BiometricDisabledWarningSample(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + WarningDialog(warning = WarningModel.BiometricsDisabledWarning(onConfirm = {}, onDismiss = {})) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun BiometricDisabledWarningPreview_Light() { + TangemTheme { + BiometricDisabledWarningSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun BiometricDisabledWarningPreview_Dark() { + TangemTheme(isDark = true) { + BiometricDisabledWarningSample() + } +} // endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt index 4940a4642a..55789fe91a 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/model/WarningModel.kt @@ -9,4 +9,9 @@ internal sealed interface WarningModel { data class KeyInvalidatedWarning( val onDismiss: () -> Unit, ) : WarningModel + + data class BiometricsDisabledWarning( + val onConfirm: () -> Unit, + val onDismiss: () -> Unit, + ) : WarningModel } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_discord.xml b/app/src/main/res/drawable/ic_discord.xml new file mode 100644 index 0000000000..88444ddf14 --- /dev/null +++ b/app/src/main/res/drawable/ic_discord.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 738e44b33c..96fb733d71 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -9,6 +9,6 @@ }, { "name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED", - "version": "4.5.0" + "version": "undefined" } ] \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f4d33131ce..e27b0df710 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -27,6 +27,7 @@ Пожалуйста, отсканируйте карту Пожалуйста, попробуйте снова через 30 секунд или отсканируйте карту Слишком много попыток + Вы отключили биометрическую аутентификацию на вашем телефоне и не сможете сохранять кошельки в приложении. Для сохранения кошельков, пожалуйста, включите функцию биометрической аутентификации в настройках телефона. Отключите эту опцию, если не хотите, чтобы эта карта использовалась для сброса кодов доступа на другие карты этого кошелька. Обратите внимание, сброс кода также не будет доступен на этой карте. Использовать эту карту для сброса кода доступа на других картах в этом кошельке Отключить возможность сброса кода доступа на этой карте или других картах этого кошелька diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index d2534d92f5..0bf985e22a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Please scan the card Please try again in 30 seconds or scan the card Too many attempts + You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings. Disable this option if you don\'t want this card to be used to reset access codes on other cards of this wallet. Note that you will not be able to reset access code on this card as well. Allows you to use this card to reset access code on other cards in this wallet Disable the ability to reset access code on this card or other cards in this wallet diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt b/domain/legacy/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt index b18597f282..39df7c6984 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/SaltPayWorkaround.kt @@ -45,7 +45,7 @@ object SaltPayWorkaround { ) + attachTestWalletCardIds() val walletCardIdRanges = listOf( - CardIdRange("AC05000000000003", "AC05000000023997")!!, + CardIdRange("AC05000000000003", "AC05000000015993")!!, ) + attachTestWalletCardIdRanges() fun tokenFrom(blockchain: Blockchain): Token {