Updated on 2026-08-14
This commit is contained in:
parent
26201c5c0b
commit
0be1358760
39 changed files with 581 additions and 124 deletions
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.card.BackupValidator
|
||||
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.card.ResetCardUseCase
|
||||
import com.tangem.domain.card.SetCardWasScannedUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
|
|
@ -32,6 +34,16 @@ internal object CardDomainModule {
|
|||
@Singleton
|
||||
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBackupValidator(): BackupValidator = BackupValidator()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsWalletBackupProblematicUseCase(backupValidator: BackupValidator): IsWalletBackupProblematicUseCase {
|
||||
return IsWalletBackupProblematicUseCase(backupValidator = backupValidator)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDerivePublicKeysUseCase(derivationsRepository: DerivationsRepository): DerivePublicKeysUseCase {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.di.domain
|
|||
import android.content.Context
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.repository.FeedbackRepository
|
||||
import dagger.Module
|
||||
|
|
@ -36,4 +37,16 @@ internal object FeedbackDomainModule {
|
|||
fun provideSaveBlockchainErrorUseCase(feedbackRepository: FeedbackRepository): SaveBlockchainErrorUseCase {
|
||||
return SaveBlockchainErrorUseCase(feedbackRepository = feedbackRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendBackupProblemEmailUseCase(
|
||||
getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
||||
sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
): SendBackupProblemEmailUseCase {
|
||||
return SendBackupProblemEmailUseCase(
|
||||
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
|
||||
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,8 @@ dependencies {
|
|||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.offramp)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.feedback)
|
||||
|
||||
implementation(deps.lifecycle.compose)
|
||||
implementation(deps.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,12 @@ import com.tangem.core.navigation.url.UrlOpener
|
|||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.offramp.GetOfframpUrlUseCase
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
|
|
@ -23,9 +26,9 @@ import com.tangem.utils.Provider
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class TokenActionsHandler @AssistedInject constructor(
|
||||
|
|
@ -39,6 +42,8 @@ class TokenActionsHandler @AssistedInject constructor(
|
|||
@Assisted private val onHandleQuickAction: (action: HandledQuickAction, shouldDismiss: Boolean) -> Unit,
|
||||
@Assisted private val coroutineScope: CoroutineScope,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
private val messageSender: UiMessageSender,
|
||||
) {
|
||||
|
||||
|
|
@ -47,6 +52,8 @@ class TokenActionsHandler @AssistedInject constructor(
|
|||
}
|
||||
|
||||
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: CryptoCurrencyData) {
|
||||
if (isTopUpBlockedByBackupError(action, cryptoCurrencyData.userWallet)) return
|
||||
|
||||
onHandleQuickAction(
|
||||
HandledQuickAction(
|
||||
action = action,
|
||||
|
|
@ -80,6 +87,21 @@ class TokenActionsHandler @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isTopUpBlockedByBackupError(action: TokenActionsBSContentUM.Action, userWallet: UserWallet): Boolean {
|
||||
val isBlockedAction = action == TokenActionsBSContentUM.Action.Buy ||
|
||||
action == TokenActionsBSContentUM.Action.Receive ||
|
||||
action == TokenActionsBSContentUM.Action.Exchange
|
||||
if (!isBlockedAction) return false
|
||||
if (!isWalletBackupProblematicUseCase(userWallet)) return false
|
||||
|
||||
messageSender.send(
|
||||
Dialogs.backupErrorAddFundsDisabled(
|
||||
onContactSupport = { coroutineScope.launch { sendBackupProblemEmailUseCase(userWallet.walletId) } },
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleDemoMode(action: TokenActionsBSContentUM.Action, userWallet: UserWallet.Cold): Boolean {
|
||||
val isDemoCard = isDemoCardUseCase.invoke(userWallet.cardId)
|
||||
val isNeededShowDemoWarning = isDemoCard && disabledActionsInDemoMode.contains(action)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
|||
subtitle = resourceReference(R.string.send_notification_invalid_amount_text),
|
||||
)
|
||||
|
||||
data class DestinationBackupError(val onContactSupport: () -> Unit) : Error(
|
||||
title = resourceReference(R.string.warning_backup_error_add_funds_title),
|
||||
subtitle = resourceReference(R.string.warning_backup_error_add_funds_message),
|
||||
buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.common_contact_support),
|
||||
onClick = onContactSupport,
|
||||
),
|
||||
)
|
||||
|
||||
data class MinimumAmountError(val amount: String) : Error(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration
|
|||
import androidx.annotation.DrawableRes
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -28,6 +29,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
|
|
@ -61,6 +63,7 @@ fun Notification(
|
|||
titleColor: Color = TangemTheme.colors.text.primary1,
|
||||
subtitleColor: Color = TangemTheme.colors.text.tertiary,
|
||||
containerColor: Color? = null,
|
||||
borderColor: Color? = null,
|
||||
iconTint: Color? = when (config.iconTint) {
|
||||
NotificationConfig.IconTint.Unspecified -> null
|
||||
NotificationConfig.IconTint.Accent -> TangemTheme.colors.icon.accent
|
||||
|
|
@ -75,6 +78,7 @@ fun Notification(
|
|||
onCloseClick = config.onCloseClick,
|
||||
modifier = modifier,
|
||||
containerColor = containerColor,
|
||||
borderColor = borderColor,
|
||||
isEnabled = isEnabled,
|
||||
) {
|
||||
MainContent(
|
||||
|
|
@ -100,6 +104,7 @@ internal fun NotificationBaseContainer(
|
|||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
containerColor: Color? = null,
|
||||
borderColor: Color? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val tempContainerColor by rememberUpdatedState(
|
||||
|
|
@ -118,6 +123,7 @@ internal fun NotificationBaseContainer(
|
|||
enabled = onClick != null && isEnabled,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
color = containerColor ?: tempContainerColor,
|
||||
border = borderColor?.let { BorderStroke(width = 1.dp, color = it) },
|
||||
) {
|
||||
Box {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -79,6 +79,24 @@ object Dialogs {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog shown when adding funds (buy / receive / swap) is blocked because the wallet has a backup problem.
|
||||
* Centralizes the wording and button behavior reused across wallet actions, token details, markets and swap.
|
||||
*
|
||||
* @param onContactSupport lambda invoked when the "Contact support" action is clicked
|
||||
*/
|
||||
fun backupErrorAddFundsDisabled(onContactSupport: () -> Unit): DialogMessage = DialogMessage(
|
||||
title = resourceReference(R.string.warning_backup_error_add_funds_title),
|
||||
message = resourceReference(R.string.warning_backup_error_add_funds_message),
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(R.string.common_contact_support),
|
||||
onClick = onContactSupport,
|
||||
)
|
||||
},
|
||||
secondActionBuilder = { cancelAction() },
|
||||
)
|
||||
|
||||
/**
|
||||
* Universal error dialog
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ object TangemColorPalette {
|
|||
// region Amaranth
|
||||
val Amaranth = Color(0xFFFF3333)
|
||||
val Amaranth_50 = Color(0x80FF3333)
|
||||
val Amaranth_30 = Color(0x4DFF3333)
|
||||
val Amaranth_20 = Color(0x33FF3333)
|
||||
val Amaranth_10 = Color(0x1AFF3333)
|
||||
// endregion Amaranth
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ android {
|
|||
}
|
||||
dependencies {
|
||||
api(projects.domain.account)
|
||||
api(projects.domain.card)
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.common)
|
||||
api(projects.domain.express)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
|||
import com.tangem.domain.account.status.utils.CryptoCurrencyMetadataCleaner
|
||||
import com.tangem.domain.account.supplier.MultiAccountListSupplier
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -50,6 +52,20 @@ internal object AccountStatusUseCaseModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetBackupProblematicWalletForAddressUseCase(
|
||||
getAccountCurrencyByAddressUseCase: GetAccountCurrencyByAddressUseCase,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
): GetBackupProblematicWalletForAddressUseCase {
|
||||
return GetBackupProblematicWalletForAddressUseCase(
|
||||
getAccountCurrencyByAddressUseCase = getAccountCurrencyByAddressUseCase,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsAccountsModeEnabledUseCase(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
|
||||
/**
|
||||
* Resolves [address] to one of the user's own wallets and returns its [UserWalletId] when that wallet
|
||||
* has a backup error (e.g. a CardLinked card), or `null` otherwise.
|
||||
*
|
||||
* Shared by the send and swap flows to block topping up a wallet with a backup problem.
|
||||
*/
|
||||
class GetBackupProblematicWalletForAddressUseCase(
|
||||
private val getAccountCurrencyByAddressUseCase: GetAccountCurrencyByAddressUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(address: String): UserWalletId? {
|
||||
val walletId = getAccountCurrencyByAddressUseCase(address).getOrNull()?.account?.userWalletId ?: return null
|
||||
val wallet = getUserWalletUseCase(walletId).getOrNull() ?: return null
|
||||
return walletId.takeIf { isWalletBackupProblematicUseCase(wallet) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.none
|
||||
import arrow.core.right
|
||||
import arrow.core.some
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrency
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class GetBackupProblematicWalletForAddressUseCaseTest {
|
||||
|
||||
private val getAccountCurrencyByAddressUseCase: GetAccountCurrencyByAddressUseCase = mockk()
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase = mockk()
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase = mockk()
|
||||
|
||||
private val useCase = GetBackupProblematicWalletForAddressUseCase(
|
||||
getAccountCurrencyByAddressUseCase = getAccountCurrencyByAddressUseCase,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(getAccountCurrencyByAddressUseCase, getUserWalletUseCase, isWalletBackupProblematicUseCase)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when address is not resolved to an account`() = runTest {
|
||||
coEvery { getAccountCurrencyByAddressUseCase(ADDRESS) } returns none()
|
||||
|
||||
assertThat(useCase(ADDRESS)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when destination wallet is not found`() = runTest {
|
||||
coEvery { getAccountCurrencyByAddressUseCase(ADDRESS) } returns accountCurrency.some()
|
||||
every { getUserWalletUseCase(walletId) } returns GetUserWalletError.UserWalletNotFound.left()
|
||||
|
||||
assertThat(useCase(ADDRESS)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns null when destination wallet is not problematic`() = runTest {
|
||||
coEvery { getAccountCurrencyByAddressUseCase(ADDRESS) } returns accountCurrency.some()
|
||||
every { getUserWalletUseCase(walletId) } returns userWallet.right()
|
||||
every { isWalletBackupProblematicUseCase(userWallet) } returns false
|
||||
|
||||
assertThat(useCase(ADDRESS)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns wallet id when destination wallet is problematic`() = runTest {
|
||||
coEvery { getAccountCurrencyByAddressUseCase(ADDRESS) } returns accountCurrency.some()
|
||||
every { getUserWalletUseCase(walletId) } returns userWallet.right()
|
||||
every { isWalletBackupProblematicUseCase(userWallet) } returns true
|
||||
|
||||
assertThat(useCase(ADDRESS)).isEqualTo(walletId)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ADDRESS = "0x1234567890abcdef"
|
||||
val walletId = UserWalletId("011")
|
||||
val userWallet = mockk<UserWallet>()
|
||||
val accountCurrency = mockk<AccountCryptoCurrency> {
|
||||
every { account } returns mockk { every { userWalletId } returns walletId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.common.card.EllipticCurve
|
|||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
||||
object BackupValidator {
|
||||
class BackupValidator {
|
||||
|
||||
fun isValidFull(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO) && validateCurves(cardDTO)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
/**
|
||||
* Single source of truth for "this wallet has a backup problem".
|
||||
*
|
||||
* A wallet is considered problematic when it is backed by a physical card whose backup was not
|
||||
* completed correctly (e.g. a card stuck in the `CardLinked` state) or when a backup error has
|
||||
* been persisted for it. Such wallets must show a prominent warning and have all top-up
|
||||
* operations blocked until the user resolves the issue with support.
|
||||
*
|
||||
* Local detection only. Can later be backed by a backend flag without changing call sites.
|
||||
*/
|
||||
class IsWalletBackupProblematicUseCase(
|
||||
private val backupValidator: BackupValidator,
|
||||
) {
|
||||
|
||||
operator fun invoke(userWallet: UserWallet): Boolean {
|
||||
if (userWallet !is UserWallet.Cold) return false
|
||||
return userWallet.hasBackupError || !backupValidator.isValidBackupStatus(userWallet.scanResponse.card)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.card
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class IsWalletBackupProblematicUseCaseTest {
|
||||
|
||||
private val backupValidator: BackupValidator = mockk()
|
||||
|
||||
private val useCase = IsWalletBackupProblematicUseCase(backupValidator = backupValidator)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(backupValidator)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false for hot wallet`() {
|
||||
val wallet = mockk<UserWallet.Hot>()
|
||||
|
||||
assertThat(useCase(wallet)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true for cold wallet with persisted backup error`() {
|
||||
val wallet = coldWallet(hasBackupError = true)
|
||||
every { backupValidator.isValidBackupStatus(any()) } returns true
|
||||
|
||||
assertThat(useCase(wallet)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true for cold wallet with invalid backup status`() {
|
||||
val wallet = coldWallet(hasBackupError = false)
|
||||
every { backupValidator.isValidBackupStatus(any()) } returns false
|
||||
|
||||
assertThat(useCase(wallet)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false for cold wallet without backup error and valid backup status`() {
|
||||
val wallet = coldWallet(hasBackupError = false)
|
||||
every { backupValidator.isValidBackupStatus(any()) } returns true
|
||||
|
||||
assertThat(useCase(wallet)).isFalse()
|
||||
}
|
||||
|
||||
private fun coldWallet(hasBackupError: Boolean): UserWallet.Cold = mockk {
|
||||
every { this@mockk.hasBackupError } returns hasBackupError
|
||||
every { scanResponse } returns mockk {
|
||||
every { card } returns mockk()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.visa.models)
|
||||
implementation(projects.domain.feedback.models)
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(projects.test.core)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
class SendBackupProblemEmailUseCase(
|
||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId) {
|
||||
val metaInfo = getWalletMetaInfoUseCase(userWalletId).getOrNull() ?: return
|
||||
sendFeedbackEmailUseCase(type = FeedbackEmailType.BackupProblem(walletMetaInfo = metaInfo))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.tangem.domain.feedback
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class SendBackupProblemEmailUseCaseTest {
|
||||
|
||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase = mockk()
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase = mockk(relaxed = true)
|
||||
|
||||
private val useCase = SendBackupProblemEmailUseCase(
|
||||
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
|
||||
sendFeedbackEmailUseCase = sendFeedbackEmailUseCase,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(getWalletMetaInfoUseCase, sendFeedbackEmailUseCase)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `does not send email when wallet meta info is unavailable`() = runTest {
|
||||
coEvery { getWalletMetaInfoUseCase(walletId) } returns Throwable().left()
|
||||
|
||||
useCase(walletId)
|
||||
|
||||
coVerify(exactly = 0) { sendFeedbackEmailUseCase(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sends backup problem email when wallet meta info is available`() = runTest {
|
||||
val metaInfo = mockk<WalletMetaInfo>()
|
||||
coEvery { getWalletMetaInfoUseCase(walletId) } returns metaInfo.right()
|
||||
|
||||
useCase(walletId)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
sendFeedbackEmailUseCase(FeedbackEmailType.BackupProblem(walletMetaInfo = metaInfo))
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val walletId = UserWalletId("011")
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ sealed class AddressValidation {
|
|||
sealed class Error : AddressValidation() {
|
||||
data object AddressInWallet : Error()
|
||||
data object InvalidAddress : Error()
|
||||
data object RecipientWalletBackupError : Error()
|
||||
data class DataError(val throwable: Throwable) : Error()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model
|
||||
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import javax.inject.Inject
|
||||
|
||||
class BackupValidator @Inject constructor() {
|
||||
|
||||
fun isValidFull(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO) && validateCurves(cardDTO)
|
||||
}
|
||||
|
||||
fun isValidBackupStatus(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO)
|
||||
}
|
||||
|
||||
private fun validateCurves(cardDTO: CardDTO): Boolean {
|
||||
val config = CardConfig.createConfig(cardDTO)
|
||||
// / Since the curve `bls12381_G2_AUG` was added later into first generation of wallets,
|
||||
// we cannot determine whether this curve is missing due to an error or because the user
|
||||
// did not want to recreate the wallet.
|
||||
val expectedCurves = config.mandatoryCurves
|
||||
.filterNot { it == EllipticCurve.Bls12381G2Aug }
|
||||
val curves = cardDTO.wallets.map { it.curve }
|
||||
for (expectedCurve in expectedCurves) {
|
||||
val cardCurvesCount = curves.count { it == expectedCurve }
|
||||
|
||||
// missing curve
|
||||
if (cardCurvesCount == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// duplicated curve
|
||||
if (cardCurvesCount > 1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun validateBackupStatus(cardDTO: CardDTO): Boolean {
|
||||
val backupStatus = cardDTO.backupStatus
|
||||
backupStatus ?: return true // for card with null backup status, validation should always returns true
|
||||
return backupStatus !is CardDTO.BackupStatus.CardLinked
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.domain.card.BackupValidator
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
|
|
@ -212,7 +213,6 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
|||
backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val backupValidator = BackupValidator()
|
||||
if (backupValidator.isValidBackupStatus(CardDTO(result.data)).not()) {
|
||||
hasWalletBackupError = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.domain.card.BackupValidator
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
|
|
@ -97,6 +98,7 @@ internal class MultiWalletFinalizeModelTest {
|
|||
every { backupService.backupCardsBatchIds } returns listOf(NON_RING_BATCH_ID, NON_RING_BATCH_ID)
|
||||
every { backupService.currentState } returns BackupService.State.FinalizingPrimaryCard
|
||||
coEvery { onboardingRepository.saveUnfinishedFinalizeOnboarding(any()) } just Runs
|
||||
every { backupValidator.isValidBackupStatus(any()) } returns true
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -427,9 +429,7 @@ internal class MultiWalletFinalizeModelTest {
|
|||
startFromFinalize = OnboardingMultiWalletState.FinalizeStage.ScanBackupFirstCard,
|
||||
)
|
||||
every { backupService.currentState } returns BackupService.State.FinalizingBackupCard(index = 1)
|
||||
|
||||
mockkConstructor(BackupValidator::class)
|
||||
every { anyConstructed<BackupValidator>().isValidBackupStatus(any()) } returns true
|
||||
every { backupValidator.isValidBackupStatus(any()) } returns true
|
||||
|
||||
val card: Card = mockk(relaxed = true)
|
||||
val callbackSlot = slot<(CompletionResult<Card>) -> Unit>()
|
||||
|
|
@ -453,8 +453,6 @@ internal class MultiWalletFinalizeModelTest {
|
|||
Assertions.assertEquals(MultiWalletFinalizeUM.Step.BackupDevice2, state.step)
|
||||
Assertions.assertEquals("backup-2-cccc".lastMaskedExpected(), state.cardNumber)
|
||||
Assertions.assertTrue(events.contains(MultiWalletFinalizeComponent.Event.TwoBackupCardsAdded))
|
||||
|
||||
unmockkConstructor(BackupValidator::class)
|
||||
}
|
||||
|
||||
private fun String.lastMaskedExpected(): String {
|
||||
|
|
|
|||
|
|
@ -25,4 +25,17 @@ internal class SendDestinationAlertFactory @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun showRecipientBackupErrorAlert(onContactSupport: () -> Unit) {
|
||||
messageSender.send(
|
||||
DialogMessage(
|
||||
title = resourceReference(id = R.string.warning_backup_error_add_funds_title),
|
||||
message = resourceReference(id = R.string.warning_backup_error_add_funds_message),
|
||||
firstAction = EventMessageAction(
|
||||
title = resourceReference(R.string.common_contact_support),
|
||||
onClick = onContactSupport,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.send.subcomponents.destination.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
|
|
@ -12,12 +13,15 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetBackupProblematicWalletForAddressUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.CryptoCurrencyAddress
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
|
|
@ -48,6 +52,7 @@ import com.tangem.features.send.subcomponents.destination.model.transformers.Sen
|
|||
import com.tangem.features.send.subcomponents.destination.model.transformers.SendDestinationValidationStartedTransformer
|
||||
import com.tangem.features.send.subcomponents.destination.ui.state.DestinationWalletUM
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.subcomponents.destination.SendDestinationAlertFactory
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
|
|
@ -58,6 +63,7 @@ import kotlinx.coroutines.awaitAll
|
|||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
|
|
@ -79,6 +85,9 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
private val getBackupProblematicWalletForAddressUseCase: GetBackupProblematicWalletForAddressUseCase,
|
||||
private val sendDestinationAlertFactory: SendDestinationAlertFactory,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
) : Model(), SendDestinationClickIntents {
|
||||
private val params: SendDestinationComponentParams = paramsContainer.require()
|
||||
|
||||
|
|
@ -95,6 +104,8 @@ internal class SendDestinationModel @Inject constructor(
|
|||
|
||||
private val validationJobHolder = JobHolder()
|
||||
|
||||
private val backupProblematicWalletCache = AtomicReference<Pair<String, UserWalletId?>?>(null)
|
||||
|
||||
init {
|
||||
configDestinationNavigation()
|
||||
subscribeOnQRScannerResult()
|
||||
|
|
@ -288,17 +299,41 @@ internal class SendDestinationModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun resolveBackupProblematicWallet(address: String): UserWalletId? {
|
||||
backupProblematicWalletCache.get()?.let { if (it.first == address) return it.second }
|
||||
|
||||
return getBackupProblematicWalletForAddressUseCase(address)
|
||||
.also { backupProblematicWalletCache.set(address to it) }
|
||||
}
|
||||
|
||||
private fun contactBackupSupport(userWalletId: UserWalletId) {
|
||||
modelScope.launch { sendBackupProblemEmailUseCase(userWalletId) }
|
||||
}
|
||||
|
||||
private fun validate(address: String, memo: String?, type: EnterAddressSource? = null) {
|
||||
modelScope.launch {
|
||||
_uiState.update(SendDestinationValidationStartedTransformer)
|
||||
|
||||
val addressValidationResult = validateWalletAddressUseCase(
|
||||
var addressValidationResult = validateWalletAddressUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
address = address,
|
||||
senderAddresses = senderAddresses.value,
|
||||
allowSelfSend = params.isAllowSelfSend,
|
||||
)
|
||||
|
||||
if (addressValidationResult.isRight()) {
|
||||
val problematicWalletId = resolveBackupProblematicWallet(address)
|
||||
if (problematicWalletId != null) {
|
||||
addressValidationResult = AddressValidation.Error.RecipientWalletBackupError.left()
|
||||
if (type != null) {
|
||||
sendDestinationAlertFactory.showRecipientBackupErrorAlert(
|
||||
onContactSupport = { contactBackupSupport(problematicWalletId) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val memoValidationResult = validateWalletMemoUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
|
|||
|
|
@ -24,14 +24,7 @@ internal class SendDestinationValidationResultTransformer(
|
|||
val isValidAddress = addressValidationResult.isRight()
|
||||
val isValidMemo = shouldDisableMemo || memoValidationResult.isRight()
|
||||
|
||||
val addressErrorText = addressValidationResult.mapLeft { error ->
|
||||
when (error) {
|
||||
is AddressValidation.Error.DataError,
|
||||
AddressValidation.Error.InvalidAddress,
|
||||
-> R.string.send_recipient_address_error
|
||||
AddressValidation.Error.AddressInWallet -> R.string.send_error_address_same_as_wallet
|
||||
}
|
||||
}.leftOrNull()
|
||||
val addressErrorText = resolveAddressErrorText()
|
||||
|
||||
val blockchainAddress =
|
||||
(addressValidationResult.getOrNull() as? AddressValidation.Success.ValidNamedAddress)?.blockchainAddress
|
||||
|
|
@ -61,6 +54,16 @@ internal class SendDestinationValidationResultTransformer(
|
|||
)
|
||||
}
|
||||
|
||||
private fun resolveAddressErrorText(): Int? = addressValidationResult.mapLeft { error ->
|
||||
when (error) {
|
||||
is AddressValidation.Error.DataError,
|
||||
AddressValidation.Error.InvalidAddress,
|
||||
-> R.string.send_recipient_address_error
|
||||
AddressValidation.Error.AddressInWallet -> R.string.send_error_address_same_as_wallet
|
||||
AddressValidation.Error.RecipientWalletBackupError -> R.string.warning_backup_error_add_funds_message
|
||||
}
|
||||
}.leftOrNull()
|
||||
|
||||
private fun buildMemoField(
|
||||
memoField: DestinationTextFieldUM.RecipientMemo?,
|
||||
isValidMemo: Boolean,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.account.status.usecase.GetBackupProblematicWalletForAddressUseCase
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.usecase.IsMemoRequiredUseCase
|
||||
import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger
|
||||
import com.tangem.features.swap.v2.impl.amount.entity.PriceImpact
|
||||
|
|
@ -28,6 +31,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -38,6 +42,8 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger,
|
||||
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
|
||||
private val isMemoRequiredUseCase: IsMemoRequiredUseCase,
|
||||
private val getBackupProblematicWalletForAddressUseCase: GetBackupProblematicWalletForAddressUseCase,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
|
@ -47,6 +53,8 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
private var notificationData = params.swapNotificationData
|
||||
private var lastSentErrorKeys: Set<Pair<String, Map<String, String>>> = emptySet()
|
||||
|
||||
private val backupProblematicWalletCache = AtomicReference<Pair<String, UserWalletId?>?>(null)
|
||||
|
||||
val uiState: StateFlow<ImmutableList<NotificationUM>>
|
||||
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
|
||||
|
||||
|
|
@ -73,6 +81,7 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
addInsufficientFundsNotification()
|
||||
addExpressErrorNotification()
|
||||
addDestinationTagRequiredNotification()
|
||||
addDestinationBackupErrorNotification()
|
||||
maybeAddPriceImpactNotification()
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +143,25 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<NotificationUM>.addDestinationBackupErrorNotification() {
|
||||
val destinationAddress = notificationData.destinationAddress
|
||||
if (destinationAddress.isEmpty()) return
|
||||
|
||||
val problematicWalletId = resolveBackupProblematicWallet(destinationAddress) ?: return
|
||||
add(
|
||||
NotificationUM.Error.DestinationBackupError(
|
||||
onContactSupport = { modelScope.launch { sendBackupProblemEmailUseCase(problematicWalletId) } },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun resolveBackupProblematicWallet(address: String): UserWalletId? {
|
||||
backupProblematicWalletCache.get()?.let { if (it.first == address) return it.second }
|
||||
|
||||
return getBackupProblematicWalletForAddressUseCase(address)
|
||||
.also { backupProblematicWalletCache.set(address to it) }
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addInsufficientFundsNotification() {
|
||||
val enteredFromAmount = notificationData.enteredFromAmount ?: return
|
||||
val balance = notificationData.fromCryptoCurrencyStatus?.value?.amount ?: return
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ dependencies {
|
|||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.swap)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.ui.utils.parseBigDecimalOrNull
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
|
|
@ -42,6 +43,7 @@ import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
|||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -49,6 +51,7 @@ import com.tangem.domain.express.models.ExpressOperationType
|
|||
import com.tangem.domain.express.models.ProviderFilterType
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
|
@ -149,6 +152,8 @@ internal class SwapModel @Inject constructor(
|
|||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val shouldShowStoriesUseCase: ShouldShowStoriesUseCase,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
private val swapInteractor: SwapInteractor,
|
||||
private val swapTransferInteractor: SwapTransferInteractor,
|
||||
private val swapTransferStateBuilder: SwapTransferStateBuilder,
|
||||
|
|
@ -472,6 +477,12 @@ internal class SwapModel @Inject constructor(
|
|||
val selectedCurrencyStatus = result.currency
|
||||
val selectedAccount = result.account.account
|
||||
|
||||
if (!isFromDirection && isWalletBackupProblematicUseCase(selectedUserWallet)) {
|
||||
router.pop()
|
||||
showBackupErrorAlert(selectedUserWallet.walletId)
|
||||
return
|
||||
}
|
||||
|
||||
val (fromSwapCurrencyStatus, toSwapCurrencyStatus) = if (isFromDirection) {
|
||||
SwapCurrencyStatus(
|
||||
userWallet = selectedUserWallet,
|
||||
|
|
@ -1862,6 +1873,14 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun showBackupErrorAlert(userWalletId: UserWalletId) {
|
||||
messageSender.send(
|
||||
Dialogs.backupErrorAddFundsDisabled(
|
||||
onContactSupport = { modelScope.launch { sendBackupProblemEmailUseCase(userWalletId) } },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun showTransactionErrorAlert(
|
||||
error: SwapTransactionState.Error,
|
||||
onSupportClick: (String) -> Unit = ::onFailedTxEmailClick,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSy
|
|||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -101,6 +103,8 @@ internal abstract class SwapModelTestBase {
|
|||
protected val getSwapUiModeUseCase: GetSwapUiModeUseCase = mockk(relaxed = true)
|
||||
protected val setSwapUiModeUseCase: SetSwapUiModeUseCase = mockk(relaxed = true)
|
||||
protected val calculateAmountUseCase: CalculateAmountUseCase = mockk(relaxed = true)
|
||||
protected val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase = mockk(relaxed = true)
|
||||
protected val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase = mockk(relaxed = true)
|
||||
|
||||
private val chooseTokenBridgeFactory: ChooseTokenBridge.Factory = mockk(relaxed = true)
|
||||
private val getUserCountryUseCase: GetUserCountryUseCase = mockk(relaxed = true)
|
||||
|
|
@ -171,6 +175,8 @@ internal abstract class SwapModelTestBase {
|
|||
getSwapUiModeUseCase = getSwapUiModeUseCase,
|
||||
setSwapUiModeUseCase = setSwapUiModeUseCase,
|
||||
calculateAmountUseCase = calculateAmountUseCase,
|
||||
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
|
||||
sendBackupProblemEmailUseCase = sendBackupProblemEmailUseCase,
|
||||
)
|
||||
|
||||
// region builders
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ dependencies {
|
|||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.dynamicAddresses)
|
||||
implementation(projects.domain.dynamicAddresses.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.notifications.models)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import javax.inject.Inject
|
||||
|
|
@ -70,4 +71,8 @@ internal class TokenDetailsDialogFactory @Inject constructor(
|
|||
fun showError(text: TextReference) {
|
||||
uiMessageSender.send(DialogMessage(message = text))
|
||||
}
|
||||
|
||||
fun showBackupError(onContactSupport: () -> Unit) {
|
||||
uiMessageSender.send(Dialogs.backupErrorAddFundsDisabled(onContactSupport = onContactSupport))
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,8 @@ import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
|||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -159,6 +161,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
|
||||
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
private val associateAssetUseCase: AssociateAssetUseCase,
|
||||
private val retryIncompleteTransactionUseCase: RetryIncompleteTransactionUseCase,
|
||||
private val openTrustlineUseCase: OpenTrustlineUseCase,
|
||||
|
|
@ -588,6 +592,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
|
||||
return
|
||||
}
|
||||
if (isTopUpBlockedByBackupError()) return
|
||||
|
||||
val status = cryptoCurrencyStatus ?: return
|
||||
modelScope.launch {
|
||||
|
|
@ -710,6 +715,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
|
||||
return
|
||||
}
|
||||
if (isTopUpBlockedByBackupError()) return
|
||||
|
||||
modelScope.launch {
|
||||
if (needShowYieldSupplyWarning()) {
|
||||
|
|
@ -824,6 +830,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
if (handleUnavailabilityReason(unavailabilityReason = unavailabilityReason)) {
|
||||
return
|
||||
}
|
||||
if (isTopUpBlockedByBackupError()) return
|
||||
|
||||
modelScope.launch {
|
||||
if (checkYieldSupply && needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)) {
|
||||
|
|
@ -1199,6 +1206,19 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun isTopUpBlockedByBackupError(): Boolean {
|
||||
if (!isWalletBackupProblematicUseCase(userWallet)) return false
|
||||
|
||||
dialogFactory.showBackupError(onContactSupport = ::contactBackupSupport)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun contactBackupSupport() {
|
||||
modelScope.launch {
|
||||
sendBackupProblemEmailUseCase(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openStaking() {
|
||||
modelScope.launch {
|
||||
getStakingAvailabilityUseCase.invokeSync(userWalletId, cryptoCurrency)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.common.ui.tokens.getUnavailabilityReasonText
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic.ButtonSupport
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -27,10 +28,12 @@ import com.tangem.domain.account.status.usecase.IsCryptoCurrencyCouldHideUseCase
|
|||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.feedback.SendBackupProblemEmailUseCase
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
|
|
@ -58,6 +61,7 @@ import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
|
|||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
|
|
@ -144,6 +148,9 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val isCryptoCurrencyCouldHideUseCase: IsCryptoCurrencyCouldHideUseCase,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(
|
||||
|
|
@ -196,6 +203,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
event?.let { analyticsEventHandler.send(it) }
|
||||
|
||||
if (isTopUpBlockedByBackupError(accountId.userWalletId)) return
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
if (needShowYieldSupplyWarning(cryptoCurrencyStatus)) {
|
||||
stateHolder.hideBottomSheet()
|
||||
|
|
@ -339,6 +348,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
|
||||
if (handleUnavailabilityReason(unavailabilityReason)) return
|
||||
if (isTopUpBlockedByBackupError(accountId.userWalletId)) return
|
||||
|
||||
appRouter.push(
|
||||
AppRoute.Onramp(
|
||||
|
|
@ -363,6 +373,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
|
||||
if (handleUnavailabilityReason(unavailabilityReason)) return
|
||||
if (isTopUpBlockedByBackupError(accountId.userWalletId)) return
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
if (needShowYieldSupplyWarning(cryptoCurrencyStatus)) {
|
||||
|
|
@ -451,6 +462,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
|
||||
if (isTopUpBlockedByBackupError(userWalletId)) return
|
||||
if (!isMultiWalletTokensLoaded()) return
|
||||
|
||||
modelScope.launch {
|
||||
|
|
@ -469,6 +481,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String) {
|
||||
if (isTopUpBlockedByBackupError(userWalletId)) return
|
||||
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
|
||||
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
|
||||
|
|
@ -565,6 +579,26 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
return true
|
||||
}
|
||||
|
||||
private fun isTopUpBlockedByBackupError(userWalletId: UserWalletId): Boolean {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return false
|
||||
if (!isWalletBackupProblematicUseCase(userWallet)) return false
|
||||
|
||||
stateHolder.hideBottomSheet()
|
||||
uiMessageSender.send(
|
||||
WalletAlertUM.addFundsDisabledForBackupError(
|
||||
onContactSupport = { contactBackupSupport(userWallet) },
|
||||
),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun contactBackupSupport(userWallet: UserWallet) {
|
||||
analyticsEventHandler.send(ButtonSupport(source = AnalyticsParam.ScreensSources.Main))
|
||||
modelScope.launch {
|
||||
sendBackupProblemEmailUseCase(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isMultiWalletTokensLoaded(): Boolean {
|
||||
return if (stateHolder.value.isRedesignEnabled) {
|
||||
val selectedWalletUM = stateHolder.getSelectedWalletUM() as? WalletUM.Content ?: return false
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.domain.card.configs.CardConfig
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import javax.inject.Inject
|
||||
|
||||
class BackupValidator @Inject constructor() {
|
||||
|
||||
fun isValidFull(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO) && validateCurves(cardDTO)
|
||||
}
|
||||
|
||||
fun isValidBackupStatus(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO)
|
||||
}
|
||||
|
||||
private fun validateCurves(cardDTO: CardDTO): Boolean {
|
||||
val config = CardConfig.createConfig(cardDTO)
|
||||
// / Since the curve `bls12381_G2_AUG` was added later into first generation of wallets,
|
||||
// we cannot determine whether this curve is missing due to an error or because the user
|
||||
// did not want to recreate the wallet.
|
||||
val expectedCurves = config.mandatoryCurves
|
||||
.filterNot { it == EllipticCurve.Bls12381G2Aug }
|
||||
val curves = cardDTO.wallets.map { it.curve }
|
||||
for (expectedCurve in expectedCurves) {
|
||||
val cardCurvesCount = curves.count { it == expectedCurve }
|
||||
|
||||
// missing curve
|
||||
if (cardCurvesCount == 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// duplicated curve
|
||||
if (cardCurvesCount > 1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun validateBackupStatus(cardDTO: CardDTO): Boolean {
|
||||
val backupStatus = cardDTO.backupStatus
|
||||
backupStatus ?: return true // for card with null backup status, validation should always returns true
|
||||
return backupStatus !is CardDTO.BackupStatus.CardLinked
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
|||
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
|
||||
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
|
||||
|
|
@ -56,7 +57,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
private val backupValidator: BackupValidator,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
|
||||
|
|
@ -118,6 +119,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
val isAddFundsBannerShown = isAddFundsBannerVisible(accountStatusList.totalFiatBalance)
|
||||
|
||||
buildList {
|
||||
addBackupErrorNotification(userWallet, clickIntents)
|
||||
|
||||
addUsedOutdatedDataNotification(accountStatusList.totalFiatBalance)
|
||||
|
||||
addAddFundsBanner(
|
||||
|
|
@ -126,7 +129,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
clickIntents = clickIntents,
|
||||
)
|
||||
|
||||
addCriticalNotifications(userWallet, clickIntents)
|
||||
addCriticalNotifications(userWallet)
|
||||
|
||||
addUpgradeHotWalletPromoNotification(
|
||||
userWallet = userWallet,
|
||||
|
|
@ -270,20 +273,22 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(
|
||||
private fun MutableList<WalletNotification>.addBackupErrorNotification(
|
||||
userWallet: UserWallet,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Critical.BackupError { clickIntents.onBackupErrorClick() },
|
||||
condition = isWalletBackupProblematicUseCase(userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(userWallet: UserWallet) {
|
||||
if (userWallet !is UserWallet.Cold) {
|
||||
return
|
||||
}
|
||||
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
addIf(
|
||||
element = WalletNotification.Critical.BackupError { clickIntents.onBackupErrorClick() },
|
||||
condition = !backupValidator.isValidBackupStatus(userWallet.scanResponse.card) || userWallet.hasBackupError,
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Critical.DevCard,
|
||||
condition = !cardTypesResolver.isReleaseFirmwareType(),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
|||
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
|
||||
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||
|
|
@ -44,7 +45,7 @@ import javax.inject.Inject
|
|||
internal class GetWalletNotificationsFactory @Inject constructor(
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
|
||||
private val backupValidator: BackupValidator,
|
||||
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
|
||||
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
|
||||
|
|
@ -167,7 +168,7 @@ internal class GetWalletNotificationsFactory @Inject constructor(
|
|||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
addIf(
|
||||
element = WalletNotificationUM.BackupError { clickIntents.onSupportClick() },
|
||||
condition = !backupValidator.isValidBackupStatus(userWallet.scanResponse.card) || userWallet.hasBackupError,
|
||||
condition = isWalletBackupProblematicUseCase(userWallet),
|
||||
)
|
||||
|
||||
addIf(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.ui.extensions.WrappedList
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
||||
|
|
@ -55,6 +56,10 @@ internal object WalletAlertUM {
|
|||
)
|
||||
}
|
||||
|
||||
fun addFundsDisabledForBackupError(onContactSupport: () -> Unit): DialogMessage {
|
||||
return Dialogs.backupErrorAddFundsDisabled(onContactSupport = onContactSupport)
|
||||
}
|
||||
|
||||
fun insufficientTokensCountForSwapping(): DialogMessage {
|
||||
return DialogMessage(
|
||||
title = resourceReference(R.string.action_buttons_swap_no_tokens_added_alert_title),
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
)
|
||||
|
||||
data class BackupError(val onSupportClick: () -> Unit) : Critical(
|
||||
title = resourceReference(R.string.warning_backup_errors_title),
|
||||
subtitle = resourceReference(R.string.warning_backup_errors_message),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
title = resourceReference(R.string.warning_backup_error_attention_title),
|
||||
subtitle = resourceReference(R.string.warning_backup_error_attention_message),
|
||||
buttonsState = ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(id = R.string.common_contact_support),
|
||||
onClick = onSupportClick,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.ui.extensions.annotatedReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.ForceDarkTheme
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.WalletNotificationTestTags
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
|
|
@ -66,6 +67,16 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
|
|||
subtitleColor = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
is WalletNotification.Critical.BackupError -> {
|
||||
Notification(
|
||||
config = item.config,
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
containerColor = TangemColorPalette.Amaranth_30,
|
||||
borderColor = TangemColorPalette.Amaranth,
|
||||
iconTint = TangemTheme.colors.icon.warning,
|
||||
subtitleColor = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
is WalletNotification.CreateTangemPayAccount -> {
|
||||
CreatePaymentAccountNotification(
|
||||
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue