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

@ -177,7 +177,7 @@ private fun Footer(
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(
id = R.string.user_wallet_list_unlock_all,
id = R.string.user_wallet_list_unlock_all_with,
stringResource(id = R.string.common_biometrics),
),
showProgress = showUnlockProgress,

View file

@ -71,6 +71,7 @@
<string name="common_biometric_authentication">биометрическую аутентификацию</string>
<string name="common_biometrics">биометрией</string>
<string name="common_buy">Купить</string>
<string name="common_buy_currency">Купить %1$s</string>
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
<string name="common_cancel">Отмена</string>
<string name="common_close">Закрыть</string>
@ -192,6 +193,7 @@
<string name="initial_message_tap_header">Приложите карту</string>
<string name="internal_error_wallet_manager_not_found">Внутренняя ошибка: не удается найти менеджер кошельков</string>
<string name="key_invalidated_warning_description">Вы обновили данные биометрии, отсканируйте свою карту для входа</string>
<string name="main_empty_tokens_list_message">Чтобы начать отслеживать свои криптоактивы и транзакции, добавьте токены</string>
<string name="main_get_bonus_subtitle">Вы успешно прошли все уроки и теперь можете получить 1INCH токены</string>
<plurals name="main_learn_subtitle">
<item quantity="one">Пройдите 3 урока и получите %d 1INCH токен на свой кошелек</item>
@ -215,6 +217,8 @@
<item quantity="other">Вам надо сгенерировать адреса для %d новых сетей, используя вашу карту</item>
</plurals>
<string name="main_warning_missing_derivation_title">Некоторые адреса отсутствуют</string>
<string name="manage_tokens_add">Добавить</string>
<string name="manage_tokens_edit">Изменить</string>
<string name="notification_title_not_enough_funds">Невозможно покрыть %1$s комиссию</string>
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
<string name="onboarding_access_code_feature_1_title">Защита</string>
@ -325,6 +329,8 @@
<item quantity="other">за %d кошельков</item>
</plurals>
<string name="referral_point_currencies_description">Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_description_prefix">Получите</string>
<string name="referral_point_currencies_description_suffix">на ваш адрес в сети %1$s%2$s за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_title">Вы</string>
<string name="referral_point_discount_description_prefix">Получит</string>
<string name="referral_point_discount_description_suffix">при покупке кошелька на сайте tangem.com</string>
@ -491,10 +497,11 @@
<string name="user_wallet_list_rename_popup_title">Переименование кошелька</string>
<string name="user_wallet_list_single_header">Одновалютные</string>
<string name="user_wallet_list_title">Мои кошельки</string>
<string name="user_wallet_list_unlock_all">Разблокировать все с %s</string>
<string name="user_wallet_list_unlock_all">Разблокировать все</string>
<string name="user_wallet_list_unlock_all_with">Разблокировать все с %s</string>
<string name="wallet_address_button_explore">История транзакций</string>
<string name="wallet_balance_blockchain_unreachable">Сеть недоступна</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступен. Попробуй позже.</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Блокчейн недоступен. Попробуйте позже.</string>
<string name="wallet_balance_loading">Баланс загружается…</string>
<string name="wallet_balance_missing_derivation">Отсканируйте карту</string>
<string name="wallet_balance_tx_in_progress">Транзакция подтверждается…</string>
@ -577,5 +584,5 @@
<string name="welcome_unlock">Войти с %s</string>
<string name="welcome_unlock_card">Сканировать карту</string>
<string name="welcome_unlock_description">Используйте %s или отсканируйте карту для входа в приложение</string>
<string name="welcome_unlock_title">С возвращением!</string>
<string name="welcome_unlock_title">C возвращением!</string>
</resources>

View file

@ -38,6 +38,7 @@
<string name="card_settings_access_code_recovery_enabled_description">允許您使用此卡重置此錢包中其他卡上的訪問密碼</string>
<string name="card_settings_access_code_recovery_footer">禁用重置此卡或此錢包中其他卡上的訪問密碼的功能</string>
<string name="card_settings_access_code_recovery_title">恢復訪問密碼</string>
<string name="card_settings_action_sheet_reset">重置</string>
<string name="card_settings_action_sheet_title">您確定要這麼做嗎?</string>
<string name="card_settings_change_access_code">更改訪問密碼</string>
<string name="card_settings_change_access_code_footer">訪問密碼將僅在此卡上更改</string>
@ -258,6 +259,8 @@
<string name="referral_error_failed_to_load_info">無法加載有關推薦計劃的消息。請稍後再試</string>
<string name="referral_error_failed_to_load_info_with_reason">無法加載有關推薦計劃的消息。原因:%s。請稍後再試</string>
<string name="referral_friends_bought_title">您的朋友買</string>
<string name="referral_point_currencies_description_prefix">會得到</string>
<string name="referral_point_currencies_description_suffix">對於你的朋友在你的 %1$s 網絡地址%2$s上購買的每個錢包</string>
<string name="referral_point_currencies_title"></string>
<string name="referral_point_discount_description_prefix">得到</string>
<string name="referral_point_discount_description_value">%s 折扣</string>
@ -277,7 +280,6 @@
<string name="reset_card_to_factory_warning_message">我了解執行此操作後,我將無法再訪問當前錢包</string>
<string name="reset_card_with_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包。您將無法恢復當前錢包或使用卡恢復訪問密碼</string>
<string name="reset_card_without_backup_to_factory_message">恢復原廠設置將從所選卡中完全刪除錢包並將其從應用程序中刪除。您將無法恢復當前錢包</string>
<string name="russian_bank_card_warning_subtitle">您有其他國家的銀行卡或銀聯卡嗎?</string>
<string name="russian_bank_card_warning_title">目前不接受俄羅斯銀行卡</string>
<string name="save_user_wallet_agreement_access_description">登錄應用程序並在不掃描卡片的情況下檢查您的資產</string>
<string name="save_user_wallet_agreement_access_title">訪問應用程序</string>
@ -402,7 +404,7 @@
<string name="user_wallet_list_rename_popup_title">重新命名錢包</string>
<string name="user_wallet_list_single_header">單一幣種</string>
<string name="user_wallet_list_title">我的錢包</string>
<string name="user_wallet_list_unlock_all">用 %s 解鎖全部</string>
<string name="user_wallet_list_unlock_all_with">用 %s 解鎖全部</string>
<string name="wallet_address_button_explore">交易記錄</string>
<string name="wallet_balance_blockchain_unreachable">網路無法使用</string>
<string name="wallet_balance_blockchain_unreachable_try_later">區塊鍊無法使用。稍後再試</string>

View file

@ -69,6 +69,7 @@
<string name="common_biometric_authentication">biometric authentication</string>
<string name="common_biometrics">biometrics</string>
<string name="common_buy">Buy</string>
<string name="common_buy_currency">Buy %1$s</string>
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
<string name="common_cancel">Cancel</string>
<string name="common_close">Close</string>
@ -134,7 +135,10 @@
<string name="custom_token_creation_error_required_field">Required field</string>
<string name="custom_token_creation_error_wrong_decimals">Decimal number must be a valid integer, no higher than %d</string>
<string name="custom_token_custom_derivation">Custom derivation</string>
<string name="custom_token_custom_derivation_placeholder">E. g. m/00\'/0000\'/0\'/0/0</string>
<string name="custom_token_custom_derivation_title">Enter custom derivation</string>
<string name="custom_token_decimals_input_title">Decimals</string>
<string name="custom_token_derivation_path">Derivation Path</string>
<string name="custom_token_derivation_path_default">Default</string>
<string name="custom_token_derivation_path_input_title">BIP44 coin type</string>
<string name="custom_token_invalid_derivation_path">The derivation path you\'ve entered is not valid</string>
@ -142,6 +146,8 @@
<string name="custom_token_name_input_title">Name</string>
<string name="custom_token_network_input_not_selected">Not selected</string>
<string name="custom_token_network_input_title">Network</string>
<string name="custom_token_network_selector_title">Token network</string>
<string name="custom_token_subtitle">You can manually add a token that is not natively supported by Tangem</string>
<string name="custom_token_token_symbol_input_placeholder">E.g. USDC</string>
<string name="custom_token_token_symbol_input_title">Token symbol</string>
<string name="custom_token_validation_error_already_added">This token/network has already been added to your list</string>
@ -193,7 +199,7 @@
<string name="initial_message_tap_header">Tap the card</string>
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens.</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string>
<string name="main_get_bonus_subtitle">You have completed all of the lessons, and are now eligible to receive your 1INCH tokens</string>
<plurals name="main_learn_subtitle">
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>
@ -213,6 +219,29 @@
<item quantity="other">You need to generate addresses for %d new networks using your card</item>
</plurals>
<string name="main_warning_missing_derivation_title">Some addresses are missing</string>
<string name="manage_tokens_add">Add</string>
<string name="manage_tokens_custom">Custom</string>
<string name="manage_tokens_edit">Edit</string>
<string name="manage_tokens_network_selector_native_subtitle">Blockchain the cryptocurrency was initially created</string>
<string name="manage_tokens_network_selector_native_title">Native network</string>
<string name="manage_tokens_network_selector_non_native_info">Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk.</string>
<string name="manage_tokens_network_selector_non_native_subtitle">Not original or primary blockchain the token is hosted</string>
<string name="manage_tokens_network_selector_non_native_title">Non-native networks</string>
<string name="manage_tokens_network_selector_other_subtitle">Blockchain the cryptocurrency was initially created</string>
<string name="manage_tokens_network_selector_other_title">Networks</string>
<string name="manage_tokens_network_selector_title">Choose networks</string>
<string name="manage_tokens_network_selector_wallet">Wallet</string>
<string name="manage_tokens_nothing_found">Couldnt find this token, you can add it manually</string>
<string name="manage_tokens_number_of_wallets">%d of %#@total_wallets@</string>
<plurals name="manage_tokens_number_of_walletstotal_wallets">
<item quantity="one">%d wallet</item>
<item quantity="other">%d wallets</item>
</plurals>
<string name="manage_tokens_search_placeholder">e.g. BTC I trust, hodl I must</string>
<string name="manage_tokens_title">Coin market cap</string>
<string name="manage_tokens_unavailable_description">The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.</string>
<string name="manage_tokens_unavailable_vote">Upvote</string>
<string name="manage_tokens_wallet_selector_title">Choose wallet</string>
<string name="notification_title_not_enough_funds">Unable to cover %1$s fee</string>
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your wallets</string>
<string name="onboarding_access_code_feature_1_title">Protect</string>
@ -322,6 +351,8 @@
<item quantity="other">for %d wallets</item>
</plurals>
<string name="referral_point_currencies_description">Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that</string>
<string name="referral_point_currencies_description_prefix">Will get</string>
<string name="referral_point_currencies_description_suffix">for each wallet bought by your friend on your %1$s network address%2$s</string>
<string name="referral_point_currencies_title">You</string>
<string name="referral_point_discount_description_prefix">Will get a</string>
<string name="referral_point_discount_description_suffix">when buying a wallet on tangem.com</string>
@ -484,7 +515,8 @@
<string name="user_wallet_list_rename_popup_title">Rename Wallet</string>
<string name="user_wallet_list_single_header">Single-currency</string>
<string name="user_wallet_list_title">My Wallets</string>
<string name="user_wallet_list_unlock_all">Unlock all with %s</string>
<string name="user_wallet_list_unlock_all">Unlock all</string>
<string name="user_wallet_list_unlock_all_with">Unlock all with %s</string>
<string name="wallet_address_button_explore">Transaction history</string>
<string name="wallet_balance_blockchain_unreachable">Network is unreachable</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Blockchain is unreachable. Try later</string>
@ -549,6 +581,7 @@
<string name="wallet_title">Tangem</string>
<string name="warning_button_can_be_better">Can be better</string>
<string name="warning_button_learn_more">Learn more</string>
<string name="warning_button_love_it">Love it!</string>
<string name="warning_button_ok">Ok, Got it!</string>
<string name="warning_button_really_cool">Really cool!</string>
<string name="warning_existential_deposit_message">%1$s network has a concept of Existential Deposit. If your account drops below %2$s it will be deactivated and any remaining funds will be destroyed.</string>
@ -559,7 +592,11 @@
<string name="warning_rate_app_message">How do you like Tangem?</string>
<string name="warning_rate_app_title">One question</string>
<string name="warning_signed_tx_previously">This card has signed transactions in the past</string>
<string name="warning_subtitle_network_unreachable">Network currently is unreachable. Please try again later.</string>
<string name="warning_subtitle_some_networks_unreachable">Some networks currently are unreachable. Please try again later.</string>
<string name="warning_testnet_card_message">This is a Testnet card. Don\'t accept it as a payment. This card must only be used for testing and development purposes.</string>
<string name="warning_title_note_top_up">Note top up</string>
<string name="warning_title_some_networks_unreachable">Some networks are unreachable</string>
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
<string name="welcome_interrupted_backup_alert_resume">Yes, resume</string>

View file

@ -1,9 +1,7 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
@ -25,9 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.*
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
@ -46,7 +42,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsSta
*/
@Composable
fun Notification(config: NotificationConfig, modifier: Modifier = Modifier, iconTint: Color? = null) {
BaseContainer(buttonsState = config.buttonsState, modifier = modifier) {
BaseContainer(buttonsState = config.buttonsState, onClick = config.onClick, modifier = modifier) {
Column(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
@ -56,6 +52,7 @@ fun Notification(config: NotificationConfig, modifier: Modifier = Modifier, icon
iconTint = iconTint,
title = config.title,
subtitle = config.subtitle,
isClickableComponent = config.onClick != null,
)
Buttons(state = config.buttonsState)
@ -71,11 +68,12 @@ fun Notification(config: NotificationConfig, modifier: Modifier = Modifier, icon
@Composable
private fun BaseContainer(
buttonsState: NotificationConfig.ButtonsState?,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
val containerColor by rememberUpdatedState(
newValue = if (buttonsState != null) {
newValue = if (buttonsState != null || onClick != null) {
TangemTheme.colors.background.primary
} else {
TangemTheme.colors.button.disabled
@ -83,9 +81,11 @@ private fun BaseContainer(
)
Surface(
onClick = onClick ?: {},
modifier = modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size62)
.fillMaxWidth(),
enabled = onClick != null,
shape = TangemTheme.shapes.roundedCornersXMedium,
color = containerColor,
) {
@ -94,15 +94,36 @@ private fun BaseContainer(
}
@Composable
private fun MainContent(iconResId: Int, iconTint: Color?, title: TextReference, subtitle: TextReference) {
Row(horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) {
private fun MainContent(
iconResId: Int,
iconTint: Color?,
title: TextReference,
subtitle: TextReference,
isClickableComponent: Boolean,
) {
Row {
Icon(
iconResId = iconResId,
tint = iconTint,
modifier = Modifier.align(alignment = Alignment.CenterVertically),
)
SpacerW(width = TangemTheme.dimens.spacing10)
TextsBlock(title = title, subtitle = subtitle)
if (isClickableComponent) {
SpacerWMax()
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
modifier = Modifier
.size(size = TangemTheme.dimens.size20)
.align(alignment = Alignment.CenterVertically),
tint = TangemTheme.colors.icon.informative,
)
}
}
}
@ -130,7 +151,7 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
Text(
text = title.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.body2,
style = TangemTheme.typography.button,
)
Text(
@ -141,16 +162,13 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Buttons(state: NotificationButtonsState?) {
AnimatedContent(targetState = state, label = "Update the buttons content") { animatedState ->
when (animatedState) {
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = animatedState)
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = animatedState)
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = animatedState)
null -> Unit
}
when (state) {
is NotificationButtonsState.SecondaryButtonConfig -> SingleSecondaryButton(config = state)
is NotificationButtonsState.PrimaryButtonConfig -> SinglePrimaryButton(config = state)
is NotificationButtonsState.PairButtonsConfig -> PairButtons(config = state)
null -> Unit
}
}
@ -266,6 +284,12 @@ private class NotificationConfigProvider : CollectionPreviewParameterProvider<No
subtitle = TextReference.Str(value = "Check your network connection"),
iconResId = R.drawable.img_attention_20,
),
NotificationConfig(
title = TextReference.Str(value = "Used card"),
subtitle = TextReference.Str(value = "The card signed transactions in the past"),
iconResId = R.drawable.ic_alert_circle_24,
onClick = {},
),
NotificationConfig(
title = TextReference.Str(value = "Your wallet hasnt been backed up"),
subtitle = TextReference.Str(value = "To protect your assets, we advise you to carry out this procedure"),

View file

@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.TextReference
* @property subtitle subtitle
* @property iconResId icon resource id
* @property buttonsState buttons state
* @property onClick lambda be invoked when notification is clicked
* @property onCloseClick lambda be invoked when close button is clicked
*
[REDACTED_AUTHOR]
@ -19,6 +20,7 @@ data class NotificationConfig(
val subtitle: TextReference,
@DrawableRes val iconResId: Int,
val buttonsState: ButtonsState? = null,
val onClick: (() -> Unit)? = null,
val onCloseClick: (() -> Unit)? = null,
) {

View file

@ -109,7 +109,13 @@ fun combinedReference(refs: WrappedList<TextReference>): TextReference {
@ReadOnlyComposable
fun TextReference.resolveReference(): String {
return when (this) {
is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray())
is TextReference.Res -> {
val args = formatArgs
.map { if (it is TextReference) it.resolveReference() else it }
.toTypedArray()
stringResource(id = id, *args)
}
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Combined -> {
@ -125,7 +131,13 @@ fun TextReference.resolveReference(): String {
/** Resolve [TextReference] as [String] using [resources] (non-composable context) */
fun TextReference.resolveReference(resources: Resources): String {
return when (this) {
is TextReference.Res -> resources.getString(id, *formatArgs.toTypedArray())
is TextReference.Res -> {
val args = formatArgs
.map { if (it is TextReference) it.resolveReference(resources) else it }
.toTypedArray()
resources.getString(id, *args)
}
is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray())
is TextReference.Str -> value
is TextReference.Combined -> {

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) }