Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-20 21:51:44 +08:00
parent 4c903fc654
commit 1485cebb6b
14 changed files with 420 additions and 397 deletions

View file

@ -381,10 +381,9 @@ internal object WalletPreviewData {
onRefresh = {},
),
notifications = persistentListOf(
WalletNotification.UnreachableNetworks,
WalletNotification.LikeTangemApp(onClick = {}),
WalletNotification.BackupCard(onClick = {}),
WalletNotification.ScanCard(onClick = {}),
WalletNotification.Critical.DevCard,
WalletNotification.MissingAddresses(missingAddressesCount = 0, onGenerateClick = {}),
WalletNotification.Warning.NetworksUnreachable,
),
bottomSheetConfig = bottomSheet,
tokenActionsBottomSheet = actionsBottomSheet,
@ -402,7 +401,7 @@ internal object WalletPreviewData {
isRefreshing = false,
onRefresh = {},
),
notifications = persistentListOf(WalletNotification.LikeTangemApp(onClick = {})),
notifications = persistentListOf(WalletNotification.Warning.NetworksUnreachable),
buttons = manageButtons,
bottomSheetConfig = bottomSheet,
marketPriceBlockState = MarketPriceBlockState.Content(

View file

@ -4,7 +4,8 @@ import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.feature.wallet.impl.R
@ -13,7 +14,6 @@ import com.tangem.feature.wallet.impl.R
*
[REDACTED_AUTHOR]
*/
// TODO: Finalize notification strings [REDACTED_JIRA]
sealed class WalletBottomSheetConfig(
open val title: TextReference,
open val subtitle: TextReference,
@ -29,61 +29,24 @@ sealed class WalletBottomSheetConfig(
@DrawableRes val iconResId: Int? = null,
)
data class UnlockWallets(
val onUnlockClick: () -> Unit,
val onScanClick: () -> Unit,
) : WalletBottomSheetConfig(
title = TextReference.Str(value = "Unlock needed"),
subtitle = TextReference.Str(
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " +
"incididunt ut labore et dolore magna aliqua.",
data class UnlockWallets(val onUnlockClick: () -> Unit, val onScanClick: () -> Unit) : WalletBottomSheetConfig(
title = resourceReference(id = R.string.common_unlock_needed),
subtitle = resourceReference(
id = R.string.unlock_wallet_description_full,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
iconResId = R.drawable.ic_locked_24,
tint = TangemColorPalette.Black,
primaryButtonConfig = ButtonConfig(text = TextReference.Str(value = "Unlock"), onClick = onUnlockClick),
primaryButtonConfig = ButtonConfig(
text = resourceReference(id = R.string.user_wallet_list_unlock_all),
onClick = onUnlockClick,
),
secondaryButtonConfig = ButtonConfig(
text = TextReference.Str(value = "Scan card"),
text = resourceReference(id = R.string.welcome_unlock_card),
onClick = onScanClick,
iconResId = R.drawable.ic_tangem_24,
),
)
data class LikeTangemApp(
val onRateTheAppClick: () -> Unit,
val onShareClick: () -> Unit,
) : WalletBottomSheetConfig(
title = TextReference.Str(value = "Like Tangem App?"),
subtitle = TextReference.Str(value = "How was your experience with our app? Let us know:"),
iconResId = R.drawable.ic_star_24,
tint = TangemColorPalette.Tangerine,
primaryButtonConfig = ButtonConfig(
text = TextReference.Str(value = "Rate the app"),
onClick = onRateTheAppClick,
),
secondaryButtonConfig = ButtonConfig(
text = TextReference.Str(value = "Share feedback"),
onClick = onShareClick,
),
)
data class CriticalWarningAlreadySignedHashes(
val onOkClick: () -> Unit,
val onCancelClick: () -> Unit,
) : WalletBottomSheetConfig(
title = TextReference.Res(
id = R.string.warning_important_security_info,
formatArgs = WrappedList(listOf("\u26A0")),
),
subtitle = TextReference.Res(id = R.string.alert_signed_hashes_message),
iconResId = R.drawable.img_attention_20,
tint = null,
primaryButtonConfig = ButtonConfig(
text = TextReference.Res(id = R.string.common_ok),
onClick = onOkClick,
),
secondaryButtonConfig = ButtonConfig(
text = TextReference.Res(id = R.string.common_cancel),
onClick = onCancelClick,
),
)
}

View file

@ -2,8 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.components
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.feature.wallet.impl.R
/**
@ -13,157 +12,144 @@ import com.tangem.feature.wallet.impl.R
*
[REDACTED_AUTHOR]
*/
// TODO: Finalize notification strings [REDACTED_JIRA]
@Immutable
sealed class WalletNotification(open val config: NotificationConfig) {
sealed class WalletNotification(val config: NotificationConfig) {
/** Clickable notification */
sealed interface Clickable {
sealed class Critical(title: TextReference, subtitle: TextReference) : WalletNotification(
config = NotificationConfig(
title = title,
subtitle = subtitle,
iconResId = R.drawable.ic_alert_circle_24,
),
) {
/** Lambda be invoked when notification is clicked */
val onClick: () -> Unit
object DevCard : Critical(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(id = R.string.alert_developer_card),
)
object DemoCard : Critical(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(id = R.string.alert_demo_message),
)
object TestNetCard : Critical(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(id = R.string.warning_testnet_card_message),
)
object FailedCardValidation : Critical(
title = resourceReference(id = R.string.warning_failed_to_verify_card_title),
subtitle = resourceReference(id = R.string.warning_failed_to_verify_card_message),
)
data class LowSignatures(val count: Int) : Critical(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(
id = R.string.warning_low_signatures_format,
formatArgs = wrappedList(count),
),
)
}
/** "Development card" notification */
object DevCard : WalletNotification(
sealed class Warning(
title: TextReference,
subtitle: TextReference,
buttonsState: NotificationConfig.ButtonsState? = null,
onClick: (() -> Unit)? = null,
) : WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(id = R.string.alert_developer_card),
iconResId = R.drawable.ic_alert_circle_24,
title = title,
subtitle = subtitle,
iconResId = R.drawable.img_attention_20,
buttonsState = buttonsState,
onClick = onClick,
),
)
) {
/** "Test card" notification */
object TestCard : WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(id = R.string.warning_testnet_card_message),
iconResId = R.drawable.ic_alert_circle_24,
),
)
data class MissingBackup(val onStartBackupClick: () -> Unit) : Warning(
title = resourceReference(id = R.string.main_no_backup_warning_title),
subtitle = resourceReference(id = R.string.main_no_backup_warning_subtitle),
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = R.string.button_start_backup_process),
onClick = onStartBackupClick,
),
)
/** "Demo card" notification */
object DemoCard : WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(id = R.string.alert_demo_message),
iconResId = R.drawable.ic_alert_circle_24,
),
)
object NetworksUnreachable : Warning(
title = resourceReference(id = R.string.wallet_balance_blockchain_unreachable),
subtitle = resourceReference(id = R.string.warning_subtitle_network_unreachable),
)
/** "Card verification failed" notification */
object CardVerificationFailed : WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.warning_failed_to_verify_card_title),
subtitle = TextReference.Res(id = R.string.warning_failed_to_verify_card_message),
iconResId = R.drawable.ic_alert_circle_24,
),
)
object SomeNetworksUnreachable : Warning(
title = resourceReference(id = R.string.warning_title_some_networks_unreachable),
subtitle = resourceReference(id = R.string.warning_subtitle_some_networks_unreachable),
)
/**
* "Remaining signatures left" notification
*
* @param count number of remaining signatures
*/
class RemainingSignaturesLeft(count: Int) : WalletNotification(
data class TopUpNote(val errorMessage: String) : Warning(
title = resourceReference(id = R.string.warning_title_note_top_up),
subtitle = stringReference(value = errorMessage),
)
object NumberOfSignedHashesIncorrect : Warning(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(id = R.string.alert_card_signed_transactions),
)
data class MultiWalletSignedHashesIncorrect(val onClick: () -> Unit) : Warning(
title = resourceReference(id = R.string.common_warning),
subtitle = resourceReference(id = R.string.warning_signed_tx_previously),
onClick = onClick,
)
}
data class MissingAddresses(val missingAddressesCount: Int, val onGenerateClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(
id = R.string.warning_low_signatures_format,
formatArgs = WrappedList(data = listOf(count)),
title = resourceReference(id = R.string.main_warning_missing_derivation_title),
subtitle = pluralReference(
id = R.plurals.main_warning_missing_derivation_description,
count = missingAddressesCount,
formatArgs = wrappedList(missingAddressesCount),
),
iconResId = R.drawable.ic_alert_circle_24,
),
)
/**
* "Already topped up and signed hashes" warning notification
*
* @property onClick lambda be invoked when notification's close button is clicked
*/
data class WarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
config = NotificationConfig(
title = TextReference.Res(id = R.string.common_warning),
subtitle = TextReference.Res(id = R.string.alert_card_signed_transactions),
iconResId = R.drawable.img_attention_20,
onCloseClick = onClick,
),
)
/**
* "Already signed hashes" critical warning notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class CriticalWarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification(
config = NotificationConfig(
title = TextReference.Res(
id = R.string.warning_important_security_info,
formatArgs = WrappedList(listOf("\u26A0")),
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(id = R.string.common_generate_addresses),
iconResId = R.drawable.ic_tangem_24,
onClick = onGenerateClick,
),
subtitle = TextReference.Res(id = R.string.warning_signed_tx_previously),
iconResId = R.drawable.img_attention_20,
),
)
/**
* "Backup the card" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class BackupCard(override val onClick: () -> Unit) : Clickable, WalletNotification(
data class UnlockWallets(val onClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = TextReference.Str(value = "Backup your card"),
subtitle = TextReference.Str(value = ""),
iconResId = R.drawable.img_attention_20,
),
)
/** "Unreachable networks" notification */
object UnreachableNetworks : WalletNotification(
config = NotificationConfig(
title = TextReference.Str(value = "Some networks are unreachable"),
subtitle = TextReference.Str(value = ""),
iconResId = R.drawable.img_attention_20,
),
)
/**
* "Like Tangem App" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class LikeTangemApp(override val onClick: () -> Unit) : Clickable, WalletNotification(
config = NotificationConfig(
title = TextReference.Str(value = "Like Tangem App?"),
subtitle = TextReference.Str(value = ""),
iconResId = R.drawable.ic_star_24,
),
)
/**
* "Scan the card" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class ScanCard(override val onClick: () -> Unit) : Clickable, WalletNotification(
config = NotificationConfig(
title = TextReference.Str(value = "Scan your card to continue"),
subtitle = TextReference.Str(value = ""),
iconResId = R.drawable.ic_tangem_24,
),
)
/**
* "Unlock wallets" notification
*
* @property onClick lambda be invoked when notification is clicked
*/
data class UnlockWallets(override val onClick: () -> Unit) : Clickable, WalletNotification(
config = NotificationConfig(
title = TextReference.Str(value = "Unlock needed"),
subtitle = TextReference.Str(value = ""),
title = resourceReference(id = R.string.common_unlock_needed),
subtitle = resourceReference(
id = R.string.unlock_wallet_description_short,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
iconResId = R.drawable.ic_locked_24,
onClick = onClick,
),
)
data class RateApp(
val onPositiveClick: () -> Unit,
val onNegativeClick: () -> Unit,
val onCloseClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.warning_rate_app_title),
subtitle = resourceReference(id = R.string.warning_rate_app_message),
iconResId = R.drawable.ic_star_24,
buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig(
primaryText = resourceReference(id = R.string.warning_button_love_it),
onPrimaryClick = onPositiveClick,
secondaryText = resourceReference(id = R.string.warning_button_can_be_better),
onSecondaryClick = onNegativeClick,
),
onCloseClick = onCloseClick,
),
)
}

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -20,8 +21,20 @@ import kotlinx.collections.immutable.ImmutableList
internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotification>, modifier: Modifier = Modifier) {
items(
items = configs,
key = { it.config.title.hashCode() },
key = { it::class.java },
contentType = { it.config::class.java },
itemContent = { Notification(config = it.config, modifier = modifier.animateItemPlacement()) },
itemContent = {
Notification(
config = it.config,
modifier = modifier.animateItemPlacement(),
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.MissingAddresses -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.Warning -> null
},
)
},
)
}

View file

@ -9,9 +9,7 @@ internal interface WalletClickIntents {
fun onBackClick()
fun onScanCardClick()
fun onScanCardNotificationClick()
fun onGenerateMissedAddressesClick()
fun onScanToUnlockWalletClick()
@ -19,16 +17,12 @@ internal interface WalletClickIntents {
fun onBackupCardClick()
fun onCriticalWarningAlreadySignedHashesClick()
fun onCloseWarningAlreadySignedHashesClick()
fun onMultiWalletSignedHashesNotificationClick()
fun onLikeTangemAppClick()
fun onRateTheAppClick()
fun onShareClick()
fun onWalletChange(index: Int)
fun onRefreshSwipe()

View file

@ -1,9 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.domain.card.GetCardWasScannedUseCase
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@ -13,126 +14,166 @@ import kotlinx.coroutines.flow.flow
/**
* Wallet notifications list factory
*
* @property wasCardScannedCallback callback that check if card was scanned
* @property isUserAlreadyRateAppCallback callback that check if card is user already rate app
* @property isDemoCardCallback callback that check if card is demo
* @property clickIntents screen click intents
* @property isDemoCardUseCase use case that check if card is demo
* @property isUserAlreadyRateAppUseCase use case that check if card is user already rate app
* @property getCardWasScannedUseCase use case that check if card was scanned
* @property clickIntents screen click intents
*
[REDACTED_AUTHOR]
*/
internal class WalletNotificationsListFactory(
private val wasCardScannedCallback: suspend (String) -> Boolean,
private val isUserAlreadyRateAppCallback: suspend () -> Boolean,
private val isDemoCardCallback: (String) -> Boolean,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase,
private val getCardWasScannedUseCase: GetCardWasScannedUseCase,
private val clickIntents: WalletClickIntents,
) {
fun create(cardTypesResolver: CardTypesResolver, tokenList: TokenList?): Flow<ImmutableList<WalletNotification>> {
// TODO: [REDACTED_JIRA] order
fun create(
cardTypesResolver: CardTypesResolver,
cryptoCurrencyList: List<CryptoCurrencyStatus.Status>,
): Flow<ImmutableList<WalletNotification>> {
return flow {
emit(
buildList {
if (cardTypesResolver.isTestCard()) {
add(element = WalletNotification.TestCard)
return@buildList
}
addCriticalNotifications(cardTypesResolver)
addRemainingSignaturesLeftNotifications(cardTypesResolver)
addMissingAddressesNotification(cryptoCurrencyList)
val isDemo = isDemoCardCallback(cardTypesResolver.getCardId())
if (!cardTypesResolver.isReleaseFirmwareType()) {
add(element = WalletNotification.DevCard)
} else {
addReleaseSpecialNotifications(cardTypesResolver = cardTypesResolver, isDemo = isDemo)
}
addRateTheAppNotification()
if (isDemo) {
add(element = WalletNotification.DemoCard)
}
if (hasUnreachableNetworks(tokenList)) {
add(element = WalletNotification.UnreachableNetworks)
}
if (!cardTypesResolver.isBackupForbidden() && !cardTypesResolver.hasBackup()) {
add(element = WalletNotification.BackupCard(onClick = clickIntents::onBackupCardClick))
}
if (tokenList != null && tokenList.hasMissedDerivations()) {
add(element = WalletNotification.ScanCard(onClick = clickIntents::onScanCardNotificationClick))
}
if (isUserAlreadyRateAppCallback()) {
add(element = WalletNotification.LikeTangemApp(onClick = clickIntents::onLikeTangemAppClick))
}
}.toImmutableList(),
addWarningNotifications(cardTypesResolver, cryptoCurrencyList)
}
.toImmutableList(),
)
}
}
private fun MutableList<WalletNotification>.addRemainingSignaturesLeftNotifications(
cardTypesResolver: CardTypesResolver,
) {
val remainingSignatures = cardTypesResolver.getRemainingSignatures()
if (remainingSignatures != null && remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT) {
add(element = WalletNotification.RemainingSignaturesLeft(remainingSignatures))
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf(
element = WalletNotification.Critical.DevCard,
condition = !cardTypesResolver.isReleaseFirmwareType(),
)
addIf(
element = WalletNotification.Critical.DemoCard,
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
)
addIf(
element = WalletNotification.Critical.TestNetCard,
condition = cardTypesResolver.isTestCard(),
)
addIf(
element = WalletNotification.Critical.FailedCardValidation,
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
)
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
addIf(
element = WalletNotification.Critical.LowSignatures(count = remainingSignatures),
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
)
}
}
private suspend fun MutableList<WalletNotification>.addReleaseSpecialNotifications(
cardTypesResolver: CardTypesResolver,
isDemo: Boolean,
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
cryptoCurrencyList: List<CryptoCurrencyStatus.Status>,
) {
if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed() &&
!isDemo
) {
if (cardTypesResolver.isBackupForbidden() && cardTypesResolver.hasWalletSignedHashes()) {
add(
element = WalletNotification.CriticalWarningAlreadySignedHashes(
onClick = clickIntents::onCriticalWarningAlreadySignedHashesClick,
),
val missedAddressesCount = cryptoCurrencyList.getMissedAddressesCount()
addIf(
element = WalletNotification.MissingAddresses(
missingAddressesCount = missedAddressesCount,
onGenerateClick = clickIntents::onGenerateMissedAddressesClick,
),
condition = missedAddressesCount > 0,
)
}
private fun List<CryptoCurrencyStatus.Status>.getMissedAddressesCount(): Int {
return filterIsInstance<CryptoCurrencyStatus.MissedDerivation>().count()
}
// TODO: [REDACTED_JIRA]
private suspend fun MutableList<WalletNotification>.addRateTheAppNotification() {
addIf(
element = WalletNotification.RateApp(
onPositiveClick = {},
onNegativeClick = {},
onCloseClick = {},
),
condition = isUserAlreadyRateAppUseCase(),
)
}
private suspend fun MutableList<WalletNotification>.addWarningNotifications(
cardTypesResolver: CardTypesResolver,
cryptoCurrencyList: List<CryptoCurrencyStatus.Status>,
) {
addIf(
element = WalletNotification.Warning.MissingBackup(
onStartBackupClick = clickIntents::onBackupCardClick,
),
condition = !cardTypesResolver.isBackupForbidden() && !cardTypesResolver.hasBackup(),
)
val isDemo = isDemoCardUseCase(cardId = cardTypesResolver.getCardId())
if (cardTypesResolver.isMultiwalletAllowed()) {
addIf(
element = WalletNotification.Warning.SomeNetworksUnreachable,
condition = cryptoCurrencyList.hasUnreachableNetworks(),
)
if (cardTypesResolver.isBackupForbidden()) {
addIf(
element = WalletNotification.Warning.NumberOfSignedHashesIncorrect,
condition = checkSignedHashes(cardTypesResolver = cardTypesResolver, isDemo = isDemo),
)
} else if (cardTypesResolver.hasWalletSignedHashes()) {
add(
element = WalletNotification.WarningAlreadySignedHashes(
onClick = clickIntents::onCloseWarningAlreadySignedHashesClick,
} else {
addIf(
element = WalletNotification.Warning.MultiWalletSignedHashesIncorrect(
onClick = clickIntents::onMultiWalletSignedHashesNotificationClick,
),
condition = checkSignedHashes(cardTypesResolver = cardTypesResolver, isDemo = isDemo),
)
}
}
} else {
addIf(
element = WalletNotification.Warning.NetworksUnreachable,
condition = cryptoCurrencyList.hasUnreachableNetworks(),
)
if (cardTypesResolver.isAttestationFailed()) {
add(element = WalletNotification.CardVerificationFailed)
// TODO: [REDACTED_JIRA]
addIf(
element = WalletNotification.Warning.TopUpNote(
errorMessage = "To activate card top up it with at least 1 XLM",
),
condition = cryptoCurrencyList.hasNoAccountStatus(),
)
addIf(
element = WalletNotification.Warning.NumberOfSignedHashesIncorrect,
condition = checkSignedHashes(cardTypesResolver, isDemo),
)
}
}
private fun hasUnreachableNetworks(tokenList: TokenList?): Boolean {
return when (tokenList) {
is TokenList.GroupedByNetwork -> {
tokenList.groups
.flatMap(NetworkGroup::currencies)
.map(CryptoCurrencyStatus::value)
.any { it is CryptoCurrencyStatus.Unreachable }
}
is TokenList.Ungrouped -> {
tokenList.currencies
.map(CryptoCurrencyStatus::value)
.any { it is CryptoCurrencyStatus.Unreachable }
}
is TokenList.NotInitialized,
null,
-> false
}
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
if (condition) add(element = element)
}
private fun TokenList.hasMissedDerivations(): Boolean {
val statuses = when (this) {
is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::currencies).map(CryptoCurrencyStatus::value)
is TokenList.Ungrouped -> currencies.map(CryptoCurrencyStatus::value)
TokenList.NotInitialized -> emptyList()
}
private fun List<CryptoCurrencyStatus.Status>.hasUnreachableNetworks(): Boolean {
return any { it is CryptoCurrencyStatus.Unreachable }
}
return statuses.any { it is CryptoCurrencyStatus.MissedDerivation }
private fun List<CryptoCurrencyStatus.Status>.hasNoAccountStatus(): Boolean {
return any { it is CryptoCurrencyStatus.NoAccount }
}
private suspend fun checkSignedHashes(cardTypesResolver: CardTypesResolver, isDemo: Boolean): Boolean {
return cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.hasWalletSignedHashes() &&
!isDemo && !getCardWasScannedUseCase(cardId = cardTypesResolver.getCardId())
}
private companion object {

View file

@ -11,7 +11,6 @@ import com.tangem.core.navigation.AppScreen
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.IsBalanceHiddenUseCase
@ -25,6 +24,7 @@ import com.tangem.domain.settings.*
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.tokens.models.Network
@ -32,7 +32,6 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.SaveWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.*
@ -41,7 +40,6 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.state.*
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory
@ -80,9 +78,6 @@ internal class WalletViewModel @Inject constructor(
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase,
private val getCardWasScannedUseCase: GetCardWasScannedUseCase,
private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val scanCardProcessor: ScanCardProcessor,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
@ -99,6 +94,9 @@ internal class WalletViewModel @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventsHandler: AnalyticsEventHandler,
getCardWasScannedUseCase: GetCardWasScannedUseCase,
isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase,
isDemoCardUseCase: IsDemoCardUseCase,
// endregion Parameters
) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents {
@ -109,9 +107,9 @@ internal class WalletViewModel @Inject constructor(
private var isBalanceHidden = true
private val notificationsListFactory = WalletNotificationsListFactory(
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
isUserAlreadyRateAppCallback = isUserAlreadyRateAppUseCase::invoke,
isDemoCardCallback = isDemoCardUseCase::invoke,
getCardWasScannedUseCase = getCardWasScannedUseCase,
isUserAlreadyRateAppUseCase = isUserAlreadyRateAppUseCase,
isDemoCardUseCase = isDemoCardUseCase,
clickIntents = this,
)
@ -254,58 +252,9 @@ internal class WalletViewModel @Inject constructor(
}
}
override fun onScanCardClick() {
override fun onGenerateMissedAddressesClick() {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.NoticeScanYourCardTapped)
viewModelScope.launch(dispatchers.io) {
scanCardProcessor.scan()
.doOnSuccess {
// If card's public key is null then user wallet will be null
val userWallet = UserWalletBuilder(scanResponse = it).build()
if (userWallet != null) {
saveWalletUseCase(userWallet = userWallet, canOverride = false)
.onLeft { saveWalletError ->
showErrorIfWalletNotSaved(
error = saveWalletError,
scannedWalletId = userWallet.walletId,
)
}
}
}
}
}
private fun showErrorIfWalletNotSaved(error: SaveWalletError, scannedWalletId: UserWalletId) {
when (error) {
is SaveWalletError.DataError -> Unit
is SaveWalletError.WalletAlreadySaved -> {
viewModelScope.launch(dispatchers.main) {
val alreadySavedWalletIndex = (uiState as? WalletState.ContentState)?.walletsListConfig
?.wallets?.indexOfFirst { it.id == scannedWalletId }
if (alreadySavedWalletIndex != null && alreadySavedWalletIndex != -1) {
uiState = stateFactory.getStateAndTriggerEvent(
state = uiState,
event = WalletEvent.ChangeWallet(index = alreadySavedWalletIndex),
setUiState = { uiState = it },
)
// Delay between events
delay(timeMillis = 500L)
}
uiState = stateFactory.getStateAndTriggerEvent(
state = uiState,
event = WalletEvent.ShowError(text = TextReference.Res(error.messageId)),
setUiState = { uiState = it },
)
}
}
}
}
override fun onScanCardNotificationClick() {
scanToUpdateSelectedWallet(
onSuccessSave = {
// Reload currencies with missed derivation
@ -361,33 +310,15 @@ internal class WalletViewModel @Inject constructor(
router.openOnboardingScreen()
}
override fun onCriticalWarningAlreadySignedHashesClick() {
uiState = stateFactory.getStateWithOpenWalletBottomSheet(
content = WalletBottomSheetConfig.CriticalWarningAlreadySignedHashes(
onOkClick = {},
onCancelClick = {},
),
)
}
override fun onCloseWarningAlreadySignedHashesClick() {
override fun onMultiWalletSignedHashesNotificationClick() {
// TODO: [REDACTED_JIRA]
}
override fun onLikeTangemAppClick() {
uiState = stateFactory.getStateWithOpenWalletBottomSheet(
content = WalletBottomSheetConfig.LikeTangemApp(
onRateTheAppClick = ::onRateTheAppClick,
onShareClick = ::onShareClick,
),
)
}
override fun onRateTheAppClick() {
// TODO: [REDACTED_JIRA]
}
override fun onShareClick() {
override fun onRateTheAppClick() {
// TODO: [REDACTED_JIRA]
}
@ -772,7 +703,19 @@ internal class WalletViewModel @Inject constructor(
private fun updateNotifications(index: Int, tokenList: TokenList? = null) {
notificationsListFactory.create(
cardTypesResolver = getCardTypeResolver(index = index),
tokenList = tokenList,
cryptoCurrencyList = if (tokenList != null) {
when (tokenList) {
is TokenList.GroupedByNetwork -> {
tokenList.groups
.flatMap(NetworkGroup::currencies)
.map(CryptoCurrencyStatus::value)
}
is TokenList.Ungrouped -> tokenList.currencies.map(CryptoCurrencyStatus::value)
is TokenList.NotInitialized -> emptyList()
}
} else {
listOfNotNull(singleWalletCryptoCurrencyStatus?.value)
},
)
.distinctUntilChanged()
.onEach { uiState = stateFactory.getStateByNotifications(notifications = it) }