Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-21 19:56:51 +05:00
parent 30a7d0dcec
commit 0c5d018677
16 changed files with 195 additions and 12 deletions

View file

@ -25,7 +25,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
@Module
@InstallIn(SingletonComponent::class)
internal object WalletsDomainModule {
@ -352,4 +352,24 @@ internal object WalletsDomainModule {
walletsRepository = walletsRepository,
)
}
@Provides
@Singleton
fun providesIsUpgradeWalletNotificationEnabledUseCase(
walletsRepository: WalletsRepository,
): IsUpgradeWalletNotificationEnabledUseCase {
return IsUpgradeWalletNotificationEnabledUseCase(
walletsRepository = walletsRepository,
)
}
@Provides
@Singleton
fun providesDismissUpgradeWalletNotificationUseCase(
walletsRepository: WalletsRepository,
): DismissUpgradeWalletNotificationUseCase {
return DismissUpgradeWalletNotificationUseCase(
walletsRepository = walletsRepository,
)
}
}

View file

@ -27,7 +27,6 @@ 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
@ -64,7 +63,6 @@ fun Notification(
NotificationConfig.IconTint.Accent -> TangemTheme.colors.icon.accent
NotificationConfig.IconTint.Attention -> TangemTheme.colors.icon.attention
},
iconSize: Dp = 20.dp,
isEnabled: Boolean = true,
) {
NotificationBaseContainer(
@ -78,7 +76,7 @@ fun Notification(
MainContent(
iconResId = config.iconResId,
iconTint = iconTint,
iconSize = iconSize,
iconSize = config.iconSize,
title = config.title,
titleColor = titleColor,
subtitle = config.subtitle,

View file

@ -1,6 +1,8 @@
package com.tangem.core.ui.components.notifications
import androidx.annotation.DrawableRes
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.TextReference
/**
@ -26,6 +28,7 @@ data class NotificationConfig(
val onCloseClick: (() -> Unit)? = null,
val showArrowIcon: Boolean = onClick != null,
val iconTint: IconTint = IconTint.Unspecified,
val iconSize: Dp = 20.dp,
) {
sealed class ButtonsState {

View file

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<group>
<clip-path
android:pathData="M0,0h36v36h-36z"/>
<path
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
android:fillColor="#EBEBEB"/>
<path
android:pathData="M21.682,9.25H14.318C13.156,9.25 12.576,9.25 12.132,9.476C11.742,9.675 11.424,9.992 11.226,10.382C11,10.826 11,11.406 11,12.568V13.8H25V12.568C25,11.406 25,10.826 24.774,10.382C24.575,9.992 24.258,9.674 23.868,9.476C23.424,9.25 22.844,9.25 21.682,9.25Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M20.333,17.65H25V23.432C25,24.594 25,25.174 24.773,25.618C24.574,26.008 24.258,26.326 23.868,26.524C23.423,26.75 22.843,26.75 21.681,26.75H20.334L20.333,17.65Z"
android:fillColor="#1E1E1E"/>
<path
android:pathData="M11,17.65H15.666V26.75H14.318C13.157,26.75 12.576,26.75 12.132,26.524C11.742,26.326 11.425,26.008 11.226,25.618C11,25.174 11,24.594 11,23.432V17.65Z"
android:fillColor="#1E1E1E"/>
</group>
</vector>

View file

@ -31,6 +31,7 @@ import com.tangem.utils.coroutines.runCatching
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.collections.mutableSetOf
typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotificationsStatus>
@ -44,6 +45,9 @@ internal class DefaultWalletsRepository(
private val authProvider: AuthProvider,
) : WalletsRepository {
private val upgradeWalletNotificationDisabled: MutableStateFlow<Set<UserWalletId>> =
MutableStateFlow(mutableSetOf<UserWalletId>())
override suspend fun shouldSaveUserWalletsSync(): Boolean {
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
}
@ -327,6 +331,16 @@ internal class DefaultWalletsRepository(
}
}
override fun isUpgradeWalletNotificationEnabled(userWalletId: UserWalletId): Flow<Boolean> {
return upgradeWalletNotificationDisabled.map {
it.contains(userWalletId)
}
}
override suspend fun dismissUpgradeWalletNotification(userWalletId: UserWalletId) {
upgradeWalletNotificationDisabled.update { it.plus(userWalletId) }
}
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
tangemTechApi.updateWallet(
walletId = walletId,

View file

@ -57,6 +57,10 @@ interface WalletsRepository {
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
fun isUpgradeWalletNotificationEnabled(userWalletId: UserWalletId): Flow<Boolean>
suspend fun dismissUpgradeWalletNotification(userWalletId: UserWalletId)
@Throws
suspend fun setWalletName(walletId: String, walletName: String)

View file

@ -0,0 +1,12 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
class DismissUpgradeWalletNotificationUseCase(
private val walletsRepository: WalletsRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId) {
walletsRepository.dismissUpgradeWalletNotification(userWalletId)
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import kotlinx.coroutines.flow.Flow
class IsUpgradeWalletNotificationEnabledUseCase(
private val walletsRepository: WalletsRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> {
return walletsRepository.isUpgradeWalletNotificationEnabled(userWalletId)
}
}

View file

@ -45,6 +45,9 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
onNotificationsDescriptionClick = {},
isNotificationsPermissionGranted = false,
onAccessCodeClick = {},
walletUpgradeDismissed = false,
onUpgradeWalletClick = {},
onDismissUpgradeWalletClick = {},
),
requestPushNotificationsPermission = false,
onPushNotificationPermissionGranted = {},

View file

@ -43,4 +43,12 @@ internal sealed class WalletSettingsItemUM {
val title: TextReference,
val description: TextReference,
) : WalletSettingsItemUM()
data class UpgradeWallet(
override val id: String,
val title: TextReference,
val description: TextReference,
val onClick: () -> Unit,
val onDismissClick: () -> Unit,
) : WalletSettingsItemUM()
}

View file

@ -10,4 +10,5 @@ internal data class WalletSettingsUM(
val requestPushNotificationsPermission: Boolean = false,
val onPushNotificationPermissionGranted: (Boolean) -> Unit,
val isWalletBackedUp: Boolean = true,
val walletUpgradeDismissed: Boolean = false,
)

View file

@ -56,7 +56,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class WalletSettingsModel @Inject constructor(
getWalletUseCase: GetUserWalletUseCase,
@ -80,6 +80,8 @@ internal class WalletSettingsModel @Inject constructor(
private val permissionsRepository: PermissionRepository,
private val notificationsRepository: NotificationsRepository,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val isUpgradeWalletNotificationEnabledUseCase: IsUpgradeWalletNotificationEnabledUseCase,
private val dismissUpgradeWalletNotificationUseCase: DismissUpgradeWalletNotificationUseCase,
) : Model() {
val params: WalletSettingsComponent.Params = paramsContainer.require()
@ -93,6 +95,7 @@ internal class WalletSettingsModel @Inject constructor(
requestPushNotificationsPermission = false,
onPushNotificationPermissionGranted = ::onPushNotificationPermissionGranted,
isWalletBackedUp = true,
walletUpgradeDismissed = false,
),
)
@ -120,7 +123,8 @@ internal class WalletSettingsModel @Inject constructor(
getWalletUseCase.invokeFlow(params.userWalletId).distinctUntilChanged(),
getWalletNFTEnabledUseCase.invoke(params.userWalletId),
getWalletNotificationsEnabledUseCase(params.userWalletId),
) { maybeWallet, nftEnabled, notificationsEnabled ->
isUpgradeWalletNotificationEnabledUseCase(params.userWalletId),
) { maybeWallet, nftEnabled, notificationsEnabled, isUpgradeNotificationEnabled ->
val wallet = maybeWallet.getOrNull() ?: return@combine
val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase()
val isWalletBackedUp = when (wallet) {
@ -139,6 +143,7 @@ internal class WalletSettingsModel @Inject constructor(
isNotificationsEnabled = notificationsEnabled,
isNotificationsFeatureEnabled = isNeedShowNotifications,
isNotificationsPermissionGranted = isNotificationsPermissionGranted(),
isUpgradeNotificationEnabled = isUpgradeNotificationEnabled,
),
isWalletBackedUp = isWalletBackedUp,
)
@ -165,6 +170,7 @@ internal class WalletSettingsModel @Inject constructor(
isNotificationsFeatureEnabled: Boolean,
isNotificationsEnabled: Boolean,
isNotificationsPermissionGranted: Boolean,
isUpgradeNotificationEnabled: Boolean,
): PersistentList<WalletSettingsItemUM> {
val isMultiCurrency = when (userWallet) {
is UserWallet.Cold -> userWallet.isMultiCurrency
@ -215,6 +221,9 @@ internal class WalletSettingsModel @Inject constructor(
onCheckedNotificationsChanged = ::onCheckedNotificationsChange,
onNotificationsDescriptionClick = ::onNotificationsDescriptionClick,
onAccessCodeClick = ::onAccessCodeClick,
walletUpgradeDismissed = isUpgradeNotificationEnabled,
onUpgradeWalletClick = ::onUpgradeWalletClick,
onDismissUpgradeWalletClick = ::onDismissUpgradeWalletClick,
)
}
@ -367,4 +376,14 @@ internal class WalletSettingsModel @Inject constructor(
router.push(AppRoute.UpdateAccessCode(params.userWalletId))
}
}
private fun onUpgradeWalletClick() {
// TODO [REDACTED_TASK_KEY]
}
private fun onDismissUpgradeWalletClick() {
modelScope.launch {
dismissUpgradeWalletNotificationUseCase.invoke(params.userWalletId)
}
}
}

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -116,6 +117,10 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) {
modifier = itemModifier,
model = item,
)
is WalletSettingsItemUM.UpgradeWallet -> UpgradeWalletBlock(
modifier = itemModifier,
model = item,
)
}
}
}
@ -225,6 +230,21 @@ private fun SwitchBlock(model: WalletSettingsItemUM.WithSwitch, modifier: Modifi
}
}
@Composable
private fun UpgradeWalletBlock(model: WalletSettingsItemUM.UpgradeWallet, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
title = model.title,
subtitle = model.description,
iconResId = R.drawable.ic_hardware_backup_36,
iconSize = 36.dp,
onClick = model.onClick,
onCloseClick = model.onDismissClick,
),
modifier = modifier,
)
}
@Composable
private fun NotificationAlertBlock(model: WalletSettingsItemUM.NotificationPermission, modifier: Modifier = Modifier) {
Notification(
@ -266,4 +286,21 @@ private fun Preview_WalletSettingsScreen() {
PreviewWalletSettingsComponent().Content(modifier = Modifier.fillMaxSize())
}
}
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_WalletSettingsScreen1() {
TangemThemePreview {
UpgradeWalletBlock(
model = WalletSettingsItemUM.UpgradeWallet(
id = "upgrade_wallet",
title = stringReference("Upgrade wallet with a hardware backup"),
description = stringReference("Keep your crypto safe with Tangems best-in-class hardware wallet."),
onClick = {},
onDismissClick = {},
),
)
}
}
// endregion Preview

View file

@ -47,8 +47,19 @@ internal class ItemsBuilder @Inject constructor(
onLinkMoreCardsClick: () -> Unit,
onReferralClick: () -> Unit,
onAccessCodeClick: () -> Unit,
walletUpgradeDismissed: Boolean,
onUpgradeWalletClick: () -> Unit,
onDismissUpgradeWalletClick: () -> Unit,
): PersistentList<WalletSettingsItemUM> = persistentListOf<WalletSettingsItemUM>()
.add(buildNameItem(userWalletName, isRenameWalletAvailable, renameWallet))
.addAll(
buildUpgradeWalletItem(
userWallet = userWallet,
walletUpgradeDismissed = walletUpgradeDismissed,
onUpgradeWalletClick = onUpgradeWalletClick,
onDismissUpgradeWalletClick = onDismissUpgradeWalletClick,
),
)
.addAll(buildAccessCodeItem(userWallet, onAccessCodeClick))
.add(
buildCardItem(
@ -124,6 +135,28 @@ internal class ItemsBuilder @Inject constructor(
onCheckedChange = onCheckedNFTChange,
)
private fun buildUpgradeWalletItem(
userWallet: UserWallet,
walletUpgradeDismissed: Boolean,
onUpgradeWalletClick: () -> Unit,
onDismissUpgradeWalletClick: () -> Unit,
): List<WalletSettingsItemUM> = when (userWallet) {
is UserWallet.Cold -> emptyList()
is UserWallet.Hot -> if (!walletUpgradeDismissed) {
listOf(
WalletSettingsItemUM.UpgradeWallet(
id = "upgrade_wallet",
title = resourceReference(id = R.string.hw_upgrade_to_cold_banner_title),
description = resourceReference(id = R.string.hw_upgrade_to_cold_banner_description),
onClick = onUpgradeWalletClick,
onDismissClick = onDismissUpgradeWalletClick,
),
)
} else {
emptyList()
}
}
private fun buildNotificationsPermissionItem() = WalletSettingsItemUM.NotificationPermission(
id = "notifications_permission",
title = resourceReference(id = R.string.transaction_notifications_warning_title),

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.pluralReference
@ -282,6 +283,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
text = resourceReference(R.string.notification_referral_promo_button),
onClick = onClick,
),
iconSize = 54.dp,
),
)
}

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
@ -39,11 +38,6 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
Notification(
config = it.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
iconSize = if (it is WalletNotification.ReferralPromo) {
54.dp
} else {
20.dp
},
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent