From 796267c863145d5a13e9f8f075455c69bc0f1871 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Sep 2025 10:18:53 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../common/ui/notifications/NotificationId.kt | 1 + .../core/ui/components/showcase/Showcase.kt | 12 ++-- .../ui/components/showcase/ShowcaseContent.kt | 3 +- .../ui/components/showcase/ShowcaseItem.kt | 34 ++++++++---- .../showcase/model/ShowcaseItemModel.kt | 3 +- .../res/drawable-hdpi/img_push_reminder.webp | Bin 0 -> 2646 bytes .../res/drawable-mdpi/img_push_reminder.webp | Bin 0 -> 1740 bytes .../res/drawable-xhdpi/img_push_reminder.webp | Bin 0 -> 4138 bytes .../drawable-xxhdpi/img_push_reminder.webp | Bin 0 -> 7614 bytes .../drawable-xxxhdpi/img_push_reminder.webp | Bin 0 -> 9572 bytes .../main/res/drawable/ic_notification_56.xml | 17 ++++++ .../drawable/ic_notification_square_24.xml | 12 ++++ core/ui/src/main/res/drawable/ic_stars_24.xml | 15 +++++ .../DefaultNotificationsRepository.kt | 6 ++ .../repository/NotificationsRepository.kt | 9 +++ .../ui/PushNotificationsBottomSheet.kt | 16 +++--- .../ui/PushNotificationsScreen.kt | 16 +++--- .../intents/WalletWarningsClickIntents.kt | 52 +++++++++++++++++- .../utils/WalletWarningsAnalyticsSender.kt | 7 ++- .../domain/GetMultiWalletWarningsFactory.kt | 52 ++++++++++++++---- .../wallet/state/model/WalletEvent.kt | 5 ++ .../wallet/state/model/WalletNotification.kt | 19 +++++++ .../wallet/ui/WalletEventEffect.kt | 39 ++++++++++++- 23 files changed, 265 insertions(+), 53 deletions(-) create mode 100644 core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp create mode 100644 core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp create mode 100644 core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp create mode 100644 core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp create mode 100644 core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp create mode 100644 core/ui/src/main/res/drawable/ic_notification_56.xml create mode 100644 core/ui/src/main/res/drawable/ic_notification_square_24.xml create mode 100644 core/ui/src/main/res/drawable/ic_stars_24.xml diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt index 4b770200d0..2cc28a8b18 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationId.kt @@ -8,4 +8,5 @@ package com.tangem.common.ui.notifications */ enum class NotificationId(val key: String) { SendViaSwapTokenSelectorNotification("SendViaSwapTokenSelectorNotificationKey"), + EnablePushesReminderNotification("EnablePushesReminderNotification"), } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt index 48f79e027f..e9347aa9b6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/Showcase.kt @@ -122,16 +122,18 @@ fun ShowcaseButtons( private fun Showcase_Preview() { TangemThemePreview { Showcase( - headerIconRes = R.drawable.ic_notifications_unread_24, + headerIconRes = R.drawable.ic_notification_56, headerText = resourceReference(R.string.user_push_notification_agreement_header), showcaseItems = persistentListOf( ShowcaseItemModel( - R.drawable.ic_rocket_launch_24, - resourceReference(R.string.user_push_notification_agreement_argument_one), + iconRes = R.drawable.ic_rocket_launch_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_one_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle), ), ShowcaseItemModel( - R.drawable.ic_storefront_24, - resourceReference(R.string.user_push_notification_agreement_argument_two), + iconRes = R.drawable.ic_storefront_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_two_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle), ), ), primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt index 336172a208..c4ff5a39e2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseContent.kt @@ -63,7 +63,8 @@ fun ShowcaseContent( repeat(showcaseItems.size) { index -> ShowcaseItem( iconRes = showcaseItems[index].iconRes, - text = showcaseItems[index].text, + title = showcaseItems[index].title, + subtitle = showcaseItems[index].subTitle, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt index 2f098c6720..4fe07dc38e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/ShowcaseItem.kt @@ -1,33 +1,47 @@ package com.tangem.core.ui.components.showcase import androidx.annotation.DrawableRes -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @Composable -internal fun ShowcaseItem(@DrawableRes iconRes: Int, text: TextReference) { - Row { +internal fun ShowcaseItem(@DrawableRes iconRes: Int, title: TextReference, subtitle: TextReference) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { Icon( painter = painterResource(id = iconRes), contentDescription = null, tint = TangemTheme.colors.icon.primary1, ) - Text( - text = text.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, + Column( modifier = Modifier .fillMaxWidth() .padding(start = TangemTheme.dimens.spacing20), - ) + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.fillMaxWidth(), + ) + + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + modifier = Modifier.fillMaxWidth(), + ) + } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt index f9865aab9e..cac2da63c0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/showcase/model/ShowcaseItemModel.kt @@ -5,5 +5,6 @@ import com.tangem.core.ui.extensions.TextReference data class ShowcaseItemModel( @DrawableRes val iconRes: Int, - val text: TextReference, + val title: TextReference, + val subTitle: TextReference, ) \ No newline at end of file diff --git a/core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp new file mode 100644 index 0000000000000000000000000000000000000000..47f81ba8f40b53e05621026f08849c22843c7a67 GIT binary patch literal 2646 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 literal 0 HcmV?d00001 diff --git a/core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp b/core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp new file mode 100644 index 0000000000000000000000000000000000000000..f30b2ac133fb0cd2ac16d83855f48bea276f3156 GIT binary patch literal 4138 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 literal 0 HcmV?d00001 diff --git a/core/ui/src/main/res/drawable/ic_notification_56.xml b/core/ui/src/main/res/drawable/ic_notification_56.xml new file mode 100644 index 0000000000..69a6c39afb --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_notification_56.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_notification_square_24.xml b/core/ui/src/main/res/drawable/ic_notification_square_24.xml new file mode 100644 index 0000000000..0a8e4b2db8 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_notification_square_24.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/ui/src/main/res/drawable/ic_stars_24.xml b/core/ui/src/main/res/drawable/ic_stars_24.xml new file mode 100644 index 0000000000..41a21a8f25 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_stars_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt index d386676472..7a9b4c6cbe 100644 --- a/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt +++ b/data/notifications/src/main/java/com/tangem/data/notifications/DefaultNotificationsRepository.kt @@ -2,11 +2,13 @@ package com.tangem.data.notifications import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.getObjectMapSync import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store import com.tangem.domain.notifications.repository.NotificationsRepository +import kotlinx.coroutines.flow.Flow import javax.inject.Inject class DefaultNotificationsRepository @Inject constructor( @@ -17,6 +19,10 @@ class DefaultNotificationsRepository @Inject constructor( return appPreferencesStore.getSyncOrDefault(PreferencesKeys.getShouldShowNotificationKey(key), true) } + override fun getShouldShowNotification(key: String): Flow { + return appPreferencesStore.get(PreferencesKeys.getShouldShowNotificationKey(key), true) + } + override suspend fun setShouldShowNotifications(key: String, value: Boolean) { appPreferencesStore.store(PreferencesKeys.getShouldShowNotificationKey(key), value) } diff --git a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt index 0f1d3cb606..00d20b0f04 100644 --- a/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt +++ b/domain/notifications/src/main/java/com/tangem/domain/notifications/repository/NotificationsRepository.kt @@ -1,5 +1,7 @@ package com.tangem.domain.notifications.repository +import kotlinx.coroutines.flow.Flow + /** * Repository interface for managing local notification logic and state. * @@ -20,6 +22,13 @@ interface NotificationsRepository { */ suspend fun shouldShowNotification(key: String): Boolean + /** + * Subscribe on whether a notification with the given [key] should be shown to the user. + * @param key The unique identifier for the notification. + * @return true if the notification should be shown, false otherwise. + */ + fun getShouldShowNotification(key: String): Flow + /** * Sets whether a notification with the given [key] should be shown to the user. * @param key The unique identifier for the notification. diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt index 07090215d9..73d7418dfc 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsBottomSheet.kt @@ -53,20 +53,18 @@ internal fun PushNotificationsContent( Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { ShowcaseContent( - headerIconRes = R.drawable.ic_notifications_unread_24, + headerIconRes = R.drawable.ic_notification_56, headerText = resourceReference(R.string.user_push_notification_agreement_header), showcaseItems = persistentListOf( ShowcaseItemModel( - R.drawable.ic_rocket_launch_24, - resourceReference(R.string.user_push_notification_agreement_argument_one), + iconRes = R.drawable.ic_notification_square_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_one_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle), ), ShowcaseItemModel( - R.drawable.ic_storefront_24, - resourceReference(R.string.user_push_notification_agreement_argument_two), - ), - ShowcaseItemModel( - R.drawable.ic_notifications_24, - resourceReference(R.string.user_push_notification_agreement_argument_three), + iconRes = R.drawable.ic_stars_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_two_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle), ), ), modifier = Modifier.padding(top = TangemTheme.dimens.spacing40), diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt index 81ec2b2743..835e3c725a 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/presentation/ui/PushNotificationsScreen.kt @@ -26,20 +26,18 @@ internal fun PushNotificationsScreen( ) Showcase( - headerIconRes = R.drawable.ic_notifications_unread_24, + headerIconRes = R.drawable.ic_notification_56, headerText = resourceReference(R.string.user_push_notification_agreement_header), showcaseItems = persistentListOf( ShowcaseItemModel( - R.drawable.ic_rocket_launch_24, - resourceReference(R.string.user_push_notification_agreement_argument_one), + iconRes = R.drawable.ic_notification_square_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_one_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle), ), ShowcaseItemModel( - R.drawable.ic_storefront_24, - resourceReference(R.string.user_push_notification_agreement_argument_two), - ), - ShowcaseItemModel( - R.drawable.ic_notifications_24, - resourceReference(R.string.user_push_notification_agreement_argument_three), + iconRes = R.drawable.ic_stars_24, + title = resourceReference(R.string.user_push_notification_agreement_argument_two_title), + subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle), ), ), primaryButton = ShowcaseButtonModel( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 55390d5b9e..88c0ad0300 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -1,18 +1,19 @@ package com.tangem.feature.wallet.child.wallet.model.intents +import android.os.Build import arrow.core.getOrElse import com.tangem.common.TangemBlogUrlBuilder import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter +import com.tangem.common.ui.notifications.NotificationId import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.domain.card.SetCardWasScannedUseCase -import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.core.wallets.UserWalletsListRepository +import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.currency.CryptoCurrency @@ -20,17 +21,21 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher +import com.tangem.domain.notifications.SetShouldShowNotificationUseCase +import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.promo.ShouldShowPromoWalletUseCase import com.tangem.domain.promo.models.PromoId import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.settings.NeverToSuggestRateAppUseCase import com.tangem.domain.settings.RemindToRateAppLaterUseCase +import com.tangem.domain.settings.repositories.PermissionRepository import com.tangem.domain.staking.StakingIdFactory import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UnlockWalletsError +import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase @@ -54,6 +59,7 @@ import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +@Suppress("TooManyFunctions") internal interface WalletWarningsClickIntents { fun onAddBackupCardClick() @@ -93,9 +99,13 @@ internal interface WalletWarningsClickIntents { fun onSeedPhraseSecondNotificationReject() fun onFinishWalletActivationClick() + + fun onAllowPermissions() + + fun onDenyPermissions() } -@Suppress("LargeClass", "LongParameterList") +@Suppress("LargeClass", "LongParameterList", "TooManyFunctions") @ModelScoped internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, @@ -121,6 +131,9 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( private val appRouter: AppRouter, private val hotWalletFeatureToggles: HotWalletFeatureToggles, private val userWalletsListRepository: UserWalletsListRepository, + private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase, + private val notificationsRepository: NotificationsRepository, + private val permissionRepository: PermissionRepository, ) : BaseWalletClickIntents(), WalletWarningsClickIntents { override fun onAddBackupCardClick() { @@ -422,6 +435,39 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( appRouter.push(AppRoute.WalletActivation(userWallet.walletId)) } + override fun onAllowPermissions() { + if (isNotificationsPermissionGranted()) { + updateSubscribeOnPushPermissions(true) + } else { + walletEventSender.send( + event = WalletEvent.RequestPushPermissions( + onAllow = { updateSubscribeOnPushPermissions(true) }, + onDeny = { updateSubscribeOnPushPermissions(false) }, + ), + ) + } + } + + override fun onDenyPermissions() = updateSubscribeOnPushPermissions(false) + + private fun updateSubscribeOnPushPermissions(shouldAllow: Boolean) { + modelScope.launch { + notificationsRepository.setUserAllowToSubscribeOnPushNotifications(shouldAllow) + setShouldShowNotificationUseCase( + key = NotificationId.EnablePushesReminderNotification.key, + value = false, + ) + } + } + + private fun isNotificationsPermissionGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissionRepository.hasRuntimePermission(android.Manifest.permission.POST_NOTIFICATIONS) + } else { + true + } + } + private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List) { coroutineScope { listOf( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 9228228cd4..62e98b08ac 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -4,7 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped -import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent +import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.* import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.Program import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification @@ -49,11 +49,11 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem is WalletNotification.Critical.BackupError -> MainScreen.BackupError is WalletNotification.NoteMigration -> MainScreen.NotePromo - is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner( + is WalletNotification.SwapPromo -> NoticePromotionBanner( source = AnalyticsParam.ScreensSources.Main, program = Program.Empty, // Use it on new promo action ) - is WalletNotification.Sepa -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner( + is WalletNotification.Sepa -> NoticePromotionBanner( source = AnalyticsParam.ScreensSources.Main, program = Program.Sepa, ) @@ -70,6 +70,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( -> null is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond + is WalletNotification.PushNotifications -> null } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 8243fa0635..fa7d183dce 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.domain import arrow.core.getOrElse +import com.tangem.common.ui.notifications.NotificationId import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState @@ -18,6 +19,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.notifications.repository.NotificationsRepository import com.tangem.domain.onramp.GetOnrampCountryUseCase import com.tangem.domain.onramp.OnrampSepaAvailableUseCase import com.tangem.domain.promo.ShouldShowPromoWalletUseCase @@ -33,16 +35,16 @@ import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles import com.tangem.lib.crypto.BlockchainUtils.isBitcoin -import com.tangem.utils.coroutines.combine6 import com.tangem.utils.extensions.addIf import com.tangem.utils.extensions.isPositive import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import timber.log.Timber import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @ModelScoped internal class GetMultiWalletWarningsFactory @Inject constructor( private val tokenListStore: MultiWalletTokenListStore, @@ -55,20 +57,31 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase, private val getOnrampCountryUseCase: GetOnrampCountryUseCase, + private val notificationsRepository: NotificationsRepository, private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, ) { + @Suppress("UNCHECKED_CAST", "MagicNumber") fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> { val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver - return combine6( - flow1 = tokenListStore.getOrThrow(userWallet.walletId), - flow2 = isReadyToShowRateAppUseCase(), - flow3 = isNeedToBackupUseCase(userWallet.walletId), - flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId), - flow5 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral), - flow6 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa), - ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus, shouldShowReferralPromo, shouldShowSepaBanner -> + return combine( + tokenListStore.getOrThrow(userWallet.walletId), + isReadyToShowRateAppUseCase(), + isNeedToBackupUseCase(userWallet.walletId), + seedPhraseNotificationUseCase(userWalletId = userWallet.walletId), + shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral), + shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa), + notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key), + ) { array -> + val maybeTokenList = array[0] as Lce + val isReadyToShowRating = array[1] as Boolean + val isNeedToBackup = array[2] as Boolean + val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus + val shouldShowReferralPromo = array[4] as Boolean + val shouldShowSepaBanner = array[5] as Boolean + val shouldShowEnablePushesReminderNotification = array[6] as Boolean + buildList { addUsedOutdatedDataNotification(maybeTokenList) @@ -84,6 +97,12 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( addWarningNotifications(cardTypesResolver, maybeTokenList, isNeedToBackup, clickIntents) + addPushReminderNotification( + clickIntents = clickIntents, + shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification && + !notificationsRepository.isUserAllowToSubscribeOnPushNotifications(), + ) + addYieldSupplyNotifications(maybeTokenList) val hasCriticalOrWarning = any { notification -> @@ -305,6 +324,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) } + private fun MutableList.addPushReminderNotification( + clickIntents: WalletClickIntents, + shouldShowPushReminderBanner: Boolean, + ) { + addIf( + element = WalletNotification.PushNotifications( + onCloseClick = clickIntents::onDenyPermissions, + onEnabledClick = clickIntents::onAllowPermissions, + ), + condition = shouldShowPushReminderBanner, + ) + } + private fun Lce.hasUnreachableNetworks(): Boolean { val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt index 662fdca3de..b3f535d8a2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt @@ -30,4 +30,9 @@ internal sealed class WalletEvent { RIGHT, } } + + data class RequestPushPermissions( + val onAllow: () -> Unit, + val onDeny: () -> Unit, + ) : WalletEvent() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index a9f3f1beb1..bc42b5dc0b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -315,4 +315,23 @@ sealed class WalletNotification(val config: NotificationConfig) { iconSize = 54.dp, ), ) + + data class PushNotifications( + val onCloseClick: () -> Unit, + val onEnabledClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(R.string.user_push_notification_banner_title), + subtitle = resourceReference(R.string.user_push_notification_banner_subtitle), + iconResId = R.drawable.img_push_reminder, + onCloseClick = onCloseClick, + buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig( + primaryText = resourceReference(id = R.string.common_enable), + onPrimaryClick = onEnabledClick, + secondaryText = resourceReference(id = R.string.common_later), + onSecondaryClick = onCloseClick, + ), + iconSize = 54.dp, + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index 6553a234d3..f1e5840afa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -3,19 +3,20 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import com.tangem.core.res.getStringSafe import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.utils.requestPermission import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.ui.utils.ReviewManagerRequester import com.tangem.feature.wallet.presentation.wallet.ui.utils.animateScrollByIndex import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolling +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION @Composable internal fun WalletEventEffect( @@ -28,6 +29,13 @@ internal fun WalletEventEffect( val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val resources = LocalContext.current.resources + + var showPermissionRequest by remember { mutableStateOf Unit, () -> Unit>?>(null) } + HandlePermissionRequest( + permissionRequestParams = showPermissionRequest, + onPermissionRequestResult = { showPermissionRequest = null }, + ) + EventEffect( event = event, onTrigger = { value -> @@ -56,7 +64,34 @@ internal fun WalletEventEffect( is WalletEvent.DemonstrateWalletsScrollPreview -> { walletsListState.demonstrateScrolling(coroutineScope = coroutineScope, direction = value.direction) } + is WalletEvent.RequestPushPermissions -> { + showPermissionRequest = value.onAllow to value.onDeny + } } }, ) +} + +@Composable +private fun HandlePermissionRequest( + permissionRequestParams: Pair<() -> Unit, () -> Unit>?, + onPermissionRequestResult: () -> Unit, +) { + val actualPermissionLauncher = permissionRequestParams?.let { (onAllowExternal, onDenyExternal) -> + requestPermission( + onAllow = { + onAllowExternal() + onPermissionRequestResult() + }, + onDeny = { + onDenyExternal() + onPermissionRequestResult() + }, + permission = PUSH_PERMISSION, + ) + } + + LaunchedEffect(permissionRequestParams) { + actualPermissionLauncher?.invoke() + } } \ No newline at end of file