From 76c5fd40323f65eb428f4bd2a6e0d15bad9a4b2b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jun 2024 21:11:19 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 39 +++--- .../domain/scanCard/UseCaseScanProcessor.kt | 6 +- .../com/tangem/core/ui/components/Buttons.kt | 3 +- .../appbar/AppBarWithAdditionalButtons.kt | 13 +- .../disclaimer/api/DisclaimerRouter.kt | 4 - .../disclaimer/impl/DisclaimerFragment.kt | 4 +- .../navigation/DefaultDisclaimerRouter.kt | 5 +- .../impl/presentation/ui/DisclaimerScreen.kt | 84 +++++++------ .../ui/DisclaimerWebViewClient.kt | 24 +++- .../viewmodel/DisclaimerViewModel.kt | 5 +- .../PushNotificationsBottomSheetConfig.kt | 11 ++ .../presentation/wallet/ui/WalletScreen.kt | 29 +++-- .../PushNotificationsBottomSheet.kt | 117 ++++++++++++++++++ .../wallet/viewmodels/WalletViewModel.kt | 37 +++++- .../viewmodels/intents/WalletClickIntents.kt | 5 +- .../WalletPushPermissionClickIntents.kt | 44 +++++++ 16 files changed, 338 insertions(+), 92 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/PushNotificationsBottomSheetConfig.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index cf81714141..dddfa0a863 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -1,15 +1,12 @@ package com.tangem.tap -import android.Manifest import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Intent import android.content.IntentFilter import android.content.pm.ActivityInfo -import android.content.pm.PackageManager import android.content.res.Configuration import android.nfc.NfcAdapter -import android.os.Build import android.os.Bundle import android.view.View import androidx.activity.SystemBarStyle @@ -21,8 +18,6 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle @@ -46,6 +41,7 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase +import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase @@ -53,7 +49,9 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent +import com.tangem.features.disclaimer.api.DisclaimerRouter import com.tangem.features.managetokens.navigation.ManageTokensUi +import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter import com.tangem.features.tester.api.TesterRouter @@ -67,6 +65,7 @@ import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.extensions.dispatchNavigationAction +import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss import com.tangem.tap.common.redux.NotificationsHandler import com.tangem.tap.domain.sdk.TangemSdkManager @@ -79,10 +78,10 @@ import com.tangem.tap.features.main.model.Toast import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.routing.RoutingComponent import com.tangem.tap.routing.configurator.AppRouterConfig import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler -import com.tangem.wallet.BuildConfig import com.tangem.wallet.R import com.tangem.wallet.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint @@ -180,6 +179,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject internal lateinit var routingComponentFactory: RoutingComponent.Factory + @Inject + lateinit var disclaimerRouter: DisclaimerRouter + + @Inject + lateinit var pushNotificationsRouter: PushNotificationsRouter + + @Inject + lateinit var cardRepository: CardRepository + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -216,7 +224,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac installRouting() initContent() - checkForNotificationPermission() observeStateUpdates() observePolkadotAccountHealthCheck() @@ -296,6 +303,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac qrScanningRouter = qrScanningRouter, emailSender = emailSender, stakingRouter = stakingRouter, + disclaimerRouter = disclaimerRouter, + pushNotificationsRouter = pushNotificationsRouter, ), ) } @@ -527,8 +536,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent))) } } else { - store.dispatchNavigationAction { replaceAll(AppRoute.Home) } lifecycleScope.launch { + val toggles = store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles) + val isEnabled = toggles.isPushNotificationsEnabled && !cardRepository.isTangemTOSAccepted() + val route = if (isEnabled) AppRoute.Disclaimer(isTosAccepted = false) else AppRoute.Home + + store.dispatchNavigationAction { replaceAll(route) } intentProcessor.handleIntent(intentWhichStartedActivity, false) } } @@ -536,16 +549,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac store.dispatch(BackupAction.CheckForUnfinishedBackup) } - private fun checkForNotificationPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - BuildConfig.LOG_ENABLED && - ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != - PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0) - } - } - private fun observePolkadotAccountHealthCheck() { lifecycleScope.launch { getPolkadotCheckHasResetUseCase() diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index 10646e2bfa..381d348980 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -59,7 +59,11 @@ internal object UseCaseScanProcessor { ), ) add(AnalyticsChain(Basic.CardWasScanned(analyticsSource))) - add(DisclaimerChain(store, disclaimerWillShow)) + val pushNotificationsToggles = + store.inject(getDependency = DaggerGraphState::pushNotificationsFeatureToggles) + if (pushNotificationsToggles.isPushNotificationsEnabled) { + add(DisclaimerChain(store, disclaimerWillShow)) + } add(CheckForOnboardingChain(store, store.state.globalState.tapWalletManager)) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index bb17fcf7b6..7ef56f015e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -86,6 +86,7 @@ fun PrimaryButton( onClick: () -> Unit, modifier: Modifier = Modifier, size: TangemButtonSize = TangemButtonSize.Default, + colors: ButtonColors = TangemButtonsDefaults.primaryButtonColors, showProgress: Boolean = false, enabled: Boolean = true, ) { @@ -94,7 +95,7 @@ fun PrimaryButton( text = text, icon = TangemButtonIconPosition.None, onClick = onClick, - colors = TangemButtonsDefaults.primaryButtonColors, + colors = colors, enabled = enabled, showProgress = showProgress, size = size, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt index dd23347e63..07f48d4787 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithAdditionalButtons.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview @@ -39,12 +40,16 @@ fun AppBarWithAdditionalButtons( modifier: Modifier = Modifier, startButton: AdditionalButton? = null, endButton: AdditionalButton? = null, + textColor: Color = TangemTheme.colors.text.primary1, + iconColor: Color = TangemTheme.colors.icon.primary1, ) { AppBarWithAdditionalButtons( text = text.resolveReference(), startButton = startButton, endButton = endButton, modifier = modifier, + textColor = textColor, + iconColor = iconColor, ) } @@ -63,6 +68,8 @@ fun AppBarWithAdditionalButtons( modifier: Modifier = Modifier, startButton: AdditionalButton? = null, endButton: AdditionalButton? = null, + textColor: Color = TangemTheme.colors.text.primary1, + iconColor: Color = TangemTheme.colors.icon.primary1, ) { Box( modifier = modifier @@ -73,7 +80,7 @@ fun AppBarWithAdditionalButtons( Icon( painter = painterResource(id = startButton.iconRes), contentDescription = null, - tint = TangemTheme.colors.icon.primary1, + tint = iconColor, modifier = Modifier .size(TangemTheme.dimens.size24) .align(Alignment.CenterStart) @@ -92,7 +99,7 @@ fun AppBarWithAdditionalButtons( Text( text = text, modifier = Modifier.align(Alignment.Center), - color = TangemTheme.colors.text.primary1, + color = textColor, maxLines = 1, style = TangemTheme.typography.subtitle1, ) @@ -101,7 +108,7 @@ fun AppBarWithAdditionalButtons( Icon( painter = painterResource(id = endButton.iconRes), contentDescription = null, - tint = TangemTheme.colors.icon.primary1, + tint = iconColor, modifier = Modifier .size(TangemTheme.dimens.size24) .align(Alignment.CenterEnd) diff --git a/features/disclaimer/api/src/main/java/com/tangem/features/disclaimer/api/DisclaimerRouter.kt b/features/disclaimer/api/src/main/java/com/tangem/features/disclaimer/api/DisclaimerRouter.kt index 5575d38b51..e990639fc8 100644 --- a/features/disclaimer/api/src/main/java/com/tangem/features/disclaimer/api/DisclaimerRouter.kt +++ b/features/disclaimer/api/src/main/java/com/tangem/features/disclaimer/api/DisclaimerRouter.kt @@ -5,8 +5,4 @@ import androidx.fragment.app.Fragment interface DisclaimerRouter { fun entryFragment(): Fragment - - companion object { - const val IS_TOS_ACCEPTED_KEY = "is_tos_accepted" - } } \ No newline at end of file diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/DisclaimerFragment.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/DisclaimerFragment.kt index e17e88299f..b53e0353d2 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/DisclaimerFragment.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/DisclaimerFragment.kt @@ -5,10 +5,10 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.fragment.app.viewModels +import com.tangem.common.routing.AppRoute.Disclaimer.Companion.IS_TOS_ACCEPTED_KEY import com.tangem.common.routing.AppRouter import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.features.disclaimer.api.DisclaimerRouter import com.tangem.features.disclaimer.impl.presentation.ui.DisclaimerScreen import com.tangem.features.disclaimer.impl.presentation.viewmodel.DisclaimerViewModel import dagger.hilt.android.AndroidEntryPoint @@ -26,7 +26,7 @@ internal class DisclaimerFragment : ComposeFragment() { private val viewModel by viewModels() private val isTosAccepted: Boolean - get() = arguments?.getBoolean(DisclaimerRouter.IS_TOS_ACCEPTED_KEY) ?: false + get() = arguments?.getBoolean(IS_TOS_ACCEPTED_KEY) ?: false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/DefaultDisclaimerRouter.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/DefaultDisclaimerRouter.kt index a49d4a94c4..2897635472 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/DefaultDisclaimerRouter.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/DefaultDisclaimerRouter.kt @@ -4,15 +4,16 @@ import androidx.fragment.app.Fragment import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.features.disclaimer.impl.DisclaimerFragment +import javax.inject.Inject -internal class DefaultDisclaimerRouter( +internal class DefaultDisclaimerRouter @Inject constructor( private val appRouter: AppRouter, ) : InnerDisclaimerRouter { override fun entryFragment(): Fragment = DisclaimerFragment.create() override fun openPushNotificationPermission() { - // todo [REDACTED_JIRA] + appRouter.push(AppRoute.PushNotification) } override fun openHome() { diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerScreen.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerScreen.kt index e9a40a7a03..7b89328a8c 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerScreen.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerScreen.kt @@ -1,6 +1,7 @@ package com.tangem.features.disclaimer.impl.presentation.ui import android.Manifest +import android.annotation.SuppressLint import android.content.res.Configuration import android.os.Build import android.view.View @@ -9,12 +10,12 @@ import android.webkit.WebView import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource @@ -28,6 +29,7 @@ import com.tangem.core.ui.components.NavigationBar3ButtonsScrim import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons import com.tangem.core.ui.components.appbar.models.AdditionalButton +import com.tangem.core.ui.components.buttons.common.TangemButtonColors import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme @@ -39,36 +41,37 @@ import com.tangem.features.disclaimer.impl.presentation.state.DummyDisclaimer @Composable internal fun DisclaimerScreen(state: DisclaimerState, onBackClick: () -> Unit) { val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - + val bottomPadding = if (state.isTosAccepted) { + bottomBarHeight + TangemTheme.dimens.size16 + } else { + bottomBarHeight + TangemTheme.dimens.size64 + } + val backgroundColor = if (state.isTosAccepted) TangemTheme.colors.background.primary else TangemColorPalette.Dark6 + val (textColor, iconColor) = if (state.isTosAccepted) { + TangemTheme.colors.text.primary1 to TangemTheme.colors.icon.primary1 + } else { + TangemColorPalette.Light4 to TangemColorPalette.Light4 + } Box( modifier = Modifier - .background(TangemColorPalette.Dark6) + .background(backgroundColor) .statusBarsPadding(), ) { - Column { + Column(modifier = Modifier.padding(bottom = bottomPadding)) { AppBarWithAdditionalButtons( text = resourceReference(R.string.disclaimer_title), startButton = AdditionalButton( iconRes = R.drawable.ic_back_24, onIconClicked = onBackClick, ).takeIf { state.isTosAccepted }, + textColor = textColor, + iconColor = iconColor, ) - DisclaimerContent(state.url) - Spacer( - modifier = Modifier - .background(TangemColorPalette.Dark6) - .height( - height = if (!state.isTosAccepted) { - bottomBarHeight - } else { - bottomBarHeight + TangemTheme.dimens.size64 - }, - ), - ) + DisclaimerContent(state.url, state.isTosAccepted) } if (!state.isTosAccepted) { - BottomFade(Modifier.align(Alignment.BottomCenter), backgroundColor = TangemColorPalette.Dark6) + BottomFade(Modifier.align(Alignment.BottomCenter), backgroundColor = backgroundColor) DisclaimerButton(state.onAccept) } else { NavigationBar3ButtonsScrim() @@ -76,10 +79,13 @@ internal fun DisclaimerScreen(state: DisclaimerState, onBackClick: () -> Unit) { } } +@SuppressLint("SetJavaScriptEnabled") @Composable -private fun ColumnScope.DisclaimerContent(url: String) { +private fun DisclaimerContent(url: String, isTosAccepted: Boolean) { val progressState = remember { mutableStateOf(ProgressState.Loading) } val webClient = remember { DisclaimerWebViewClient(progressState) } + val transparent = Color.Transparent + val backgroundColor = if (isTosAccepted) TangemTheme.colors.background.primary else TangemColorPalette.Dark6 Box( modifier = Modifier, ) { @@ -90,12 +96,17 @@ private fun ColumnScope.DisclaimerContent(url: String) { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, ) - setBackgroundColor(TangemColorPalette.Dark6.toArgb()) + setBackgroundColor(transparent.toArgb()) settings.allowFileAccess = false - settings.javaScriptEnabled = false - + // to inject css style to display only in dark theme + settings.javaScriptEnabled = !isTosAccepted overScrollMode = View.OVER_SCROLL_NEVER webViewClient = webClient + + clearHistory() + clearFormData() + clearCache(true) + loadUrl(url) } }, @@ -103,7 +114,11 @@ private fun ColumnScope.DisclaimerContent(url: String) { when (progressState.value) { ProgressState.Loading -> { - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .background(backgroundColor), + ) { CircularProgressIndicator( color = TangemTheme.colors.icon.informative, modifier = Modifier @@ -112,25 +127,6 @@ private fun ColumnScope.DisclaimerContent(url: String) { ) } } - ProgressState.Error -> Box(modifier = Modifier.fillMaxSize()) { - Column( - verticalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(TangemTheme.dimens.spacing16) - .align(Alignment.Center), - ) { - Text( - text = stringResource(id = R.string.disclaimer_error_loading), - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.constantWhite, - ) - PrimaryButton( - text = stringResource(R.string.common_retry), - onClick = { webClient.reset() }, - ) - } - } else -> Unit } } @@ -147,6 +143,12 @@ private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) { PrimaryButton( text = stringResource(id = R.string.common_accept), onClick = { onAccept(shouldAskPushPermission) }, + colors = TangemButtonColors( + backgroundColor = TangemColorPalette.Light4, + contentColor = TangemColorPalette.Dark6, + disabledBackgroundColor = TangemColorPalette.Light4, + disabledContentColor = TangemColorPalette.Dark6, + ), modifier = Modifier .align(Alignment.BottomCenter) .navigationBarsPadding() diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerWebViewClient.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerWebViewClient.kt index 348cad4107..a0b568833b 100644 --- a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerWebViewClient.kt +++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerWebViewClient.kt @@ -10,6 +10,22 @@ internal enum class ProgressState { Error, } +/** + * Workaround to display web view with ToS only in dark theme + */ +private fun WebView.injectCSS() { + val code = "javascript:(function() {" + + "var node = document.createElement('style');" + + "node.type = 'text/css';" + + " node.innerHTML = 'body, label,th,p,a, td, tr,li,ul,span,table,h1,h2,h3,h4,h5,h6,h7,div,small {" + + " color: #C9C9C9;" + + "background-color: #1E1E1E;" + + " } ';" + + " document.head.appendChild(node);})();" + + evaluateJavascript(code, null) +} + internal class DisclaimerWebViewClient(private val progressState: MutableState) : WebViewClient() { private var loadingUrl: String? = null @@ -22,20 +38,23 @@ internal class DisclaimerWebViewClient(private val progressState: MutableState

Unit, + val onAllowed: () -> Unit, + val onDeny: () -> Unit, + val openSettings: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 3dde33fa2b..0a72f0cf4e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -37,6 +37,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.tangem.core.ui.components.* import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.components.atoms.handComposableComponentHeight +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet @@ -53,6 +54,7 @@ import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder +import com.tangem.feature.wallet.presentation.wallet.ui.components.PushNotificationsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.common.* @@ -204,17 +206,7 @@ private fun WalletContent( organizeTokens(state = selectedWallet, itemModifier = itemModifier) } - val bottomSheetConfig = selectedWallet.bottomSheetConfig - if (bottomSheetConfig != null) { - when (bottomSheetConfig.content) { - is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) - is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) - is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) - is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) - is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) - is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) - } - } + ShowBottomSheet(bottomSheetConfig = selectedWallet.bottomSheetConfig) WalletsListEffects( lazyListState = walletsListState, @@ -606,6 +598,21 @@ internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modi } } +@Composable +private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { + if (bottomSheetConfig != null) { + when (bottomSheetConfig.content) { + is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) + is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) + is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) + is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) + is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) + is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) + is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig) + } + } +} + // region Preview @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt new file mode 100644 index 0000000000..8522dc3b72 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/PushNotificationsBottomSheet.kt @@ -0,0 +1,117 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.showcase.ShowcaseContent +import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.requestPushPermission +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig +import kotlinx.collections.immutable.persistentListOf + +@Composable +internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet(config = config) { + PushNotificationsSheetContent(content = it, onDismiss = config.onDismissRequest) + } +} + +@Composable +private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetConfig, onDismiss: () -> Unit) { + val isClicked = remember { mutableStateOf(false) } + val requestPushPermission = requestPushPermission( + isFirstTimeAsking = content.isFirstTimeAsking, + isClicked = isClicked, + onAllow = { + content.onAllowed() + onDismiss() + }, + onDeny = { + content.onDeny() + onDismiss() + }, + onOpenSettings = content.openSettings, + ) + + Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) { + ShowcaseContent( + headerIconRes = R.drawable.ic_notifications_unread_24, + headerText = resourceReference(R.string.user_push_notification_agreement_header), + showcaseItems = persistentListOf( + ShowcaseItemModel( + iconRes = R.drawable.ic_rocket_launch_24, + text = resourceReference(R.string.user_push_notification_agreement_argument_one), + ), + ShowcaseItemModel( + iconRes = R.drawable.ic_storefront_24, + text = resourceReference(R.string.user_push_notification_agreement_argument_two), + ), + ), + modifier = Modifier.padding(top = TangemTheme.dimens.spacing40), + ) + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + modifier = Modifier.padding( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing40, + bottom = TangemTheme.dimens.spacing16, + ), + ) { + SecondaryButton( + text = stringResource(R.string.common_later), + onClick = { + content.onDeny() + onDismiss() + }, + modifier = Modifier.weight(1f), + ) + PrimaryButton( + text = stringResource(R.string.common_allow), + onClick = { + isClicked.value = true + content.onAllow() + requestPushPermission() + }, + modifier = Modifier.weight(1f), + ) + } + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun PushNotificationsSheetContent_Preview() { + TangemThemePreview { + PushNotificationsSheetContent( + PushNotificationsBottomSheetConfig( + isFirstTimeAsking = false, + onAllow = {}, + onAllowed = {}, + onDeny = {}, + openSettings = {}, + ), + onDismiss = {}, + ) + } +} +// endregion \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 6d6961b577..c4242a3c48 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -4,11 +4,11 @@ import androidx.compose.runtime.MutableState import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.navigation.settings.SettingsManager import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.settings.CanUseBiometryUseCase -import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled -import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase +import com.tangem.domain.settings.* import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.domain.wallets.usecase.GetWalletsUseCase @@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWal import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent.DemonstrateWalletsScrollPreview.Direction import com.tangem.feature.wallet.presentation.wallet.state.model.WalletPullToRefreshConfig @@ -28,6 +29,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.managetokens.navigation.ExpandableState +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder @@ -60,6 +62,10 @@ internal class WalletViewModel @Inject constructor( private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender, private val walletDeepLinksHandler: WalletDeepLinksHandler, private val walletNameMigrationUseCase: WalletNameMigrationUseCase, + private val shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase, + private val isFirstTimeAskingPermissionUseCase: IsFirstTimeAskingPermissionUseCase, + private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, + private val settingsManager: SettingsManager, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -80,6 +86,7 @@ internal class WalletViewModel @Inject constructor( subscribeOnBalanceHiding() subscribeOnSelectedWalletFlow() subscribeToScreenBackgroundState() + subscribeOnPushNotificationsPermission() } private fun maybeMigrateNames() { @@ -145,6 +152,30 @@ internal class WalletViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnPushNotificationsPermission() { + viewModelScope.launch { + if (!shouldAskPermissionUseCase(PUSH_PERMISSION)) return@launch + withContext(dispatchers.io) { delay(timeMillis = 1_800) } + + val isFirstTimeAsking = isFirstTimeAskingPermissionUseCase(PUSH_PERMISSION).getOrElse { true } + val wasInitiallyAsk = shouldInitiallyAskPermissionUseCase(PUSH_PERMISSION).getOrElse { true } + val onDenyClick: () -> Unit = if (wasInitiallyAsk) { + clickIntents::onDelayAskPushPermission + } else { + clickIntents::onNeverAskPushPermission + } + stateHolder.showBottomSheet( + PushNotificationsBottomSheetConfig( + isFirstTimeAsking = isFirstTimeAsking, + onAllow = clickIntents::onAllowPushPermission, + onAllowed = clickIntents::onNeverAskPushPermission, + onDeny = onDenyClick, + openSettings = settingsManager::openSettings, + ), + ) + } + } + private fun subscribeOnSelectedWalletFlow() { getSelectedWalletUseCase().onRight { it diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 3266406422..712975635b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -30,6 +30,7 @@ internal class WalletClickIntents @Inject constructor( private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor, private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor, private val visaWalletIntentsImplementor: VisaWalletIntentsImplementor, + private val pushPermissionClickIntentsImplementor: WalletPushPermissionClickIntentsImplementor, private val stateHolder: WalletStateController, private val walletScreenContentLoader: WalletScreenContentLoader, private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, @@ -45,7 +46,8 @@ internal class WalletClickIntents @Inject constructor( WalletWarningsClickIntents by warningsClickIntentsImplementer, WalletCurrencyActionsClickIntents by currencyActionsClickIntentsImplementor, WalletContentClickIntents by contentClickIntentsImplementor, - VisaWalletIntents by visaWalletIntentsImplementor { + VisaWalletIntents by visaWalletIntentsImplementor, + WalletPushPermissionClickIntents by pushPermissionClickIntentsImplementor { override fun initialize(router: InnerWalletRouter, coroutineScope: CoroutineScope) { super.initialize(router, coroutineScope) @@ -55,6 +57,7 @@ internal class WalletClickIntents @Inject constructor( currencyActionsClickIntentsImplementor.initialize(router, coroutineScope) contentClickIntentsImplementor.initialize(router, coroutineScope) visaWalletIntentsImplementor.initialize(router, coroutineScope) + pushPermissionClickIntentsImplementor.initialize(router, coroutineScope) } fun onWalletChange(index: Int) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt new file mode 100644 index 0000000000..3f5138ca6d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletPushPermissionClickIntents.kt @@ -0,0 +1,44 @@ +package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents + +import com.tangem.domain.settings.DelayPermissionRequestUseCase +import com.tangem.domain.settings.NeverRequestPermissionUseCase +import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase +import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.launch +import javax.inject.Inject + +internal interface WalletPushPermissionClickIntents { + + fun onAllowPushPermission() + + fun onDelayAskPushPermission() + + fun onNeverAskPushPermission() +} + +@ViewModelScoped +internal class WalletPushPermissionClickIntentsImplementor @Inject constructor( + private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase, + private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase, + private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase, +) : BaseWalletClickIntents(), WalletPushPermissionClickIntents { + + override fun onAllowPushPermission() { + viewModelScope.launch { + setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION) + } + } + + override fun onDelayAskPushPermission() { + viewModelScope.launch { + delayPermissionRequestUseCase(PUSH_PERMISSION) + } + } + + override fun onNeverAskPushPermission() { + viewModelScope.launch { + neverRequestPermissionUseCase(PUSH_PERMISSION) + } + } +} \ No newline at end of file