Updated on 2026-08-14
This commit is contained in:
commit
d9d3ba2cda
33 changed files with 413 additions and 111 deletions
|
|
@ -136,6 +136,10 @@ dependencies {
|
|||
implementation(projects.features.staking.impl)
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.details.impl)
|
||||
implementation(projects.features.disclaimer.api)
|
||||
implementation(projects.features.disclaimer.impl)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
implementation(projects.features.pushNotifications.impl)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
|
||||
|
|
@ -116,4 +117,6 @@ interface ApplicationEntryPoint {
|
|||
fun getShareManager(): ShareManager
|
||||
|
||||
fun getAppRouter(): AppRouter
|
||||
|
||||
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
|
||||
}
|
||||
|
|
@ -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<AppThemeMode?>
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.tap.common.analytics.AnalyticsFactory
|
||||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
|
|
@ -193,6 +194,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private val appRouter: AppRouter
|
||||
get() = entryPoint.getAppRouter()
|
||||
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles
|
||||
get() = entryPoint.getPushNotificationsFeatureToggles()
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -284,6 +288,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
urlOpener = urlOpener,
|
||||
shareManager = shareManager,
|
||||
appRouter = appRouter,
|
||||
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import com.tangem.core.navigation.email.EmailSender
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
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
|
||||
|
|
@ -27,5 +29,7 @@ sealed interface DaggerGraphAction : Action {
|
|||
val qrScanningRouter: QrScanningRouter,
|
||||
val emailSender: EmailSender,
|
||||
val stakingRouter: StakingRouter,
|
||||
val disclaimerRouter: DisclaimerRouter,
|
||||
val pushNotificationsRouter: PushNotificationsRouter,
|
||||
) : DaggerGraphAction
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ object DaggerGraphReducer {
|
|||
qrScanningRouter = action.qrScanningRouter,
|
||||
emailSender = action.emailSender,
|
||||
stakingRouter = action.stakingRouter,
|
||||
disclaimerRouter = action.disclaimerRouter,
|
||||
pushNotificationsRouter = action.pushNotificationsRouter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.disclaimer.api.DisclaimerRouter
|
||||
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
|
||||
import com.tangem.features.managetokens.navigation.ManageTokensUi
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
|
@ -85,4 +88,7 @@ data class DaggerGraphState(
|
|||
val urlOpener: UrlOpener? = null,
|
||||
val shareManager: ShareManager? = null,
|
||||
val appRouter: AppRouter? = null,
|
||||
val disclaimerRouter: DisclaimerRouter? = null,
|
||||
val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles? = null,
|
||||
val pushNotificationsRouter: PushNotificationsRouter? = null,
|
||||
) : StateType
|
||||
|
|
@ -7,6 +7,9 @@ import com.tangem.feature.referral.ReferralFragment
|
|||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.disclaimer.api.DisclaimerRouter
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
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
|
||||
|
|
@ -48,6 +51,9 @@ internal class ChildFactory @Inject constructor(
|
|||
private val stakingRouter: StakingRouter,
|
||||
private val testerRouter: TesterRouter,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
|
||||
private val pushNotificationRouter: PushNotificationsRouter,
|
||||
private val disclaimerRouter: DisclaimerRouter,
|
||||
) {
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
|
@ -97,7 +103,11 @@ internal class ChildFactory @Inject constructor(
|
|||
route.asFragmentChild(Provider { SecurityModeFragment() })
|
||||
}
|
||||
is AppRoute.Disclaimer -> {
|
||||
route.asFragmentChild(Provider { DisclaimerFragment() })
|
||||
if (pushNotificationsFeatureToggles.isPushNotificationsEnabled) {
|
||||
route.asFragmentChild(Provider { disclaimerRouter.entryFragment() })
|
||||
} else {
|
||||
route.asFragmentChild(Provider { DisclaimerFragment() })
|
||||
}
|
||||
}
|
||||
is AppRoute.Home -> {
|
||||
route.asFragmentChild(Provider { HomeFragment() })
|
||||
|
|
@ -147,6 +157,9 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.Staking -> {
|
||||
route.asFragmentChild(Provider { stakingRouter.getEntryFragment() })
|
||||
}
|
||||
is AppRoute.PushNotification -> {
|
||||
route.asFragmentChild(Provider { pushNotificationRouter.entryFragment() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,4 +197,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
const val YIELD_KEY = "yield"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object PushNotification : AppRoute(path = "/push_notification")
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
|
|
@ -14,17 +12,18 @@ import com.google.accompanist.permissions.shouldShowRationale
|
|||
* Returns push permission requester.
|
||||
* Handles granting permission from app settings.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun requestPushPermission(
|
||||
isFirstTimeAsking: Boolean,
|
||||
pushPermission: String?,
|
||||
isClicked: MutableState<Boolean>,
|
||||
onAllow: () -> Unit,
|
||||
onDeny: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
): () -> Unit {
|
||||
val permissionState = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val permission = Manifest.permission.POST_NOTIFICATIONS
|
||||
val permissionState = pushPermission?.let { permission ->
|
||||
val tempPermissionState = rememberPermissionState(permission = permission)
|
||||
rememberPermissionState(permission = permission) {
|
||||
when {
|
||||
|
|
@ -33,8 +32,6 @@ fun requestPushPermission(
|
|||
else -> onDeny()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Check if user granted permission and close bottom sheet
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
|
||||
/* Project - API */
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.disclaimer.api)
|
||||
implementation(projects.features.tester.api)
|
||||
|
||||
/* Project - Core */
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ dependencies {
|
|||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.accompanist.permission)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.material)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.ui)
|
||||
|
|
@ -35,12 +36,13 @@ dependencies {
|
|||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.settings)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.disclaimer.api)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
implementation(project(":domain:settings"))
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -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<DisclaimerViewModel>()
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
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
|
||||
import android.view.ViewGroup
|
||||
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 +27,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
|
||||
|
|
@ -35,40 +35,42 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.disclaimer.impl.R
|
||||
import com.tangem.features.disclaimer.impl.presentation.state.DisclaimerState
|
||||
import com.tangem.features.disclaimer.impl.presentation.state.DummyDisclaimer
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
|
||||
@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 +78,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 +95,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 +113,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 +126,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
|
||||
}
|
||||
}
|
||||
|
|
@ -139,14 +134,18 @@ private fun ColumnScope.DisclaimerContent(url: String) {
|
|||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
|
||||
val shouldAskPushPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS).status.isGranted
|
||||
} else {
|
||||
true
|
||||
}
|
||||
val shouldAskPushPermission = getPushPermissionOrNull()?.let { permission ->
|
||||
rememberPermissionState(permission = permission).status.isGranted
|
||||
} ?: true
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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<ProgressState>) : WebViewClient() {
|
||||
|
||||
private var loadingUrl: String? = null
|
||||
|
|
@ -22,20 +38,23 @@ internal class DisclaimerWebViewClient(private val progressState: MutableState<P
|
|||
}
|
||||
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
view?.injectCSS()
|
||||
super.onPageStarted(view, url, favicon)
|
||||
|
||||
if (loadingUrl != url) progressState.value = ProgressState.Loading
|
||||
if (loadingUrl != url && progressState.value != ProgressState.Error) progressState.value = ProgressState.Loading
|
||||
loadingUrl = url
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
view?.injectCSS()
|
||||
super.onPageFinished(view, url)
|
||||
|
||||
if (loadedUrl != url) progressState.value = ProgressState.Done
|
||||
if (loadedUrl != url && progressState.value != ProgressState.Error) progressState.value = ProgressState.Done
|
||||
loadedUrl = url
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView?, resourceRequest: WebResourceRequest?, error: WebResourceError?) {
|
||||
view?.injectCSS()
|
||||
super.onReceivedError(view, resourceRequest, error)
|
||||
error?.let { progressState.value = ProgressState.Error }
|
||||
}
|
||||
|
|
@ -45,6 +64,7 @@ internal class DisclaimerWebViewClient(private val progressState: MutableState<P
|
|||
resourceRequest: WebResourceRequest?,
|
||||
errorResponse: WebResourceResponse?,
|
||||
) {
|
||||
view?.injectCSS()
|
||||
super.onReceivedHttpError(view, resourceRequest, errorResponse)
|
||||
|
||||
if (resourceRequest != null && errorResponse != null) {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.routing.AppRoute.Disclaimer.Companion.IS_TOS_ACCEPTED_KEY
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.features.disclaimer.api.DisclaimerRouter.Companion.IS_TOS_ACCEPTED_KEY
|
||||
import com.tangem.features.disclaimer.impl.navigation.DefaultDisclaimerRouter
|
||||
import com.tangem.features.disclaimer.impl.presentation.state.DisclaimerState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -31,8 +31,7 @@ internal class DisclaimerViewModel @Inject constructor(
|
|||
override fun onAccept(shouldAskPushPermission: Boolean) {
|
||||
viewModelScope.launch {
|
||||
cardRepository.acceptTangemTOS()
|
||||
// todo [REDACTED_JIRA]
|
||||
disclaimerRouter.openHome()
|
||||
disclaimerRouter.openPushNotificationPermission()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ package com.tangem.features.pushnotifications.api.featuretoggles
|
|||
*/
|
||||
interface PushNotificationsFeatureToggles {
|
||||
/** Availability of push notifications */
|
||||
val isRedesignedSendEnabled: Boolean
|
||||
val isPushNotificationsEnabled: Boolean
|
||||
}
|
||||
|
|
@ -2,9 +2,19 @@ package com.tangem.features.pushnotifications.api.utils
|
|||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
|
||||
val PUSH_PERMISSION = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
|
||||
private val isRequirePushPermission = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
||||
val PUSH_PERMISSION = if (isRequirePushPermission) {
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
} else {
|
||||
"android.permission.POST_NOTIFICATIONS"
|
||||
}
|
||||
|
||||
fun getPushPermissionOrNull() = if (isRequirePushPermission) {
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -28,6 +28,10 @@ dependencies {
|
|||
/** Core modules */
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -40,6 +44,5 @@ dependencies {
|
|||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
implementation(project(":core:navigation"))
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -6,6 +6,6 @@ import com.tangem.features.pushnotifications.api.featuretoggles.PushNotification
|
|||
internal class DefaultPushNotificationsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : PushNotificationsFeatureToggles {
|
||||
override val isRedesignedSendEnabled: Boolean
|
||||
override val isPushNotificationsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("PUSH_NOTIFICATIONS_ENABLED")
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.utils.requestPushPermission
|
||||
import com.tangem.feature.pushnotifications.impl.R
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
|
|
@ -27,6 +28,7 @@ internal fun PushNotificationsScreen(
|
|||
onAllow = onAllow,
|
||||
onDeny = onLater,
|
||||
onOpenSettings = onOpenSettings,
|
||||
pushPermission = getPushPermissionOrNull(),
|
||||
)
|
||||
|
||||
Showcase(
|
||||
|
|
|
|||
|
|
@ -89,4 +89,5 @@ dependencies {
|
|||
implementation(projects.features.tester.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.model
|
||||
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
data class PushNotificationsBottomSheetConfig(
|
||||
val isFirstTimeAsking: Boolean,
|
||||
val onRequest: () -> Unit,
|
||||
val onAllow: () -> Unit,
|
||||
val onDeny: () -> Unit,
|
||||
val openSettings: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
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 com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun PushNotificationsBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<PushNotificationsBottomSheetConfig>(config = config) {
|
||||
PushNotificationsSheetContent(content = it, onDismiss = config.onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PushNotificationsSheetContent(content: PushNotificationsBottomSheetConfig, onDismiss: () -> Unit) {
|
||||
val isClicked = remember { mutableStateOf(false) }
|
||||
val requestPushPermission = requestPushPermission(
|
||||
pushPermission = getPushPermissionOrNull(),
|
||||
isFirstTimeAsking = content.isFirstTimeAsking,
|
||||
isClicked = isClicked,
|
||||
onAllow = {
|
||||
content.onAllow()
|
||||
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.onRequest()
|
||||
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,
|
||||
onRequest = {},
|
||||
onAllow = {},
|
||||
onDeny = {},
|
||||
openSettings = {},
|
||||
),
|
||||
onDismiss = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -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,31 @@ internal class WalletViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun subscribeOnPushNotificationsPermission() {
|
||||
viewModelScope.launch {
|
||||
if (!shouldAskPermissionUseCase(PUSH_PERMISSION)) return@launch
|
||||
|
||||
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,
|
||||
onRequest = clickIntents::onRequestPushPermission,
|
||||
onAllow = clickIntents::onNeverAskPushPermission,
|
||||
onDeny = onDenyClick,
|
||||
openSettings = settingsManager::openSettings,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectedWalletFlow() {
|
||||
getSelectedWalletUseCase().onRight {
|
||||
it
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 onRequestPushPermission()
|
||||
|
||||
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 onRequestPushPermission() {
|
||||
viewModelScope.launch {
|
||||
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDelayAskPushPermission() {
|
||||
viewModelScope.launch {
|
||||
delayPermissionRequestUseCase(PUSH_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNeverAskPushPermission() {
|
||||
viewModelScope.launch {
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue