diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt b/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt
new file mode 100644
index 0000000000..b51d1c0f08
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/BoxWithGradient.kt
@@ -0,0 +1,41 @@
+package com.tangem.core.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+
+@Composable
+fun BoxWithGradient(
+ modifier: Modifier = Modifier,
+ gradient: Brush = BottomGradient,
+ content: @Composable BoxScope.() -> Unit,
+) {
+ val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
+
+ Box(modifier = modifier.fillMaxSize()) {
+ content()
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size164 + bottomInsetsPx.dp)
+ .background(gradient),
+ )
+ }
+}
+
+private val BottomGradient: Brush = Brush.verticalGradient(
+ colors = listOf(
+ TangemColorPalette.Black.copy(alpha = 0f),
+ TangemColorPalette.Black.copy(alpha = 0.75f),
+ TangemColorPalette.Black.copy(alpha = 0.95f),
+ TangemColorPalette.Black,
+ ),
+)
\ No newline at end of file
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 685358db13..dd23347e63 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
@@ -1,23 +1,52 @@
package com.tangem.core.ui.components.appbar
import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.Text
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.appbar.models.AdditionalButton
-import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+
+/**
+ * App bar with title and two additional buttons
+ *
+ * @param startButton button information attached to the left edge
+ * @param endButton button information attached to the right edge
+ *
+ * @see
+ * Figma Component
+ */
+@Composable
+fun AppBarWithAdditionalButtons(
+ text: TextReference,
+ modifier: Modifier = Modifier,
+ startButton: AdditionalButton? = null,
+ endButton: AdditionalButton? = null,
+) {
+ AppBarWithAdditionalButtons(
+ text = text.resolveReference(),
+ startButton = startButton,
+ endButton = endButton,
+ modifier = modifier,
+ )
+}
/**
* App bar with title and two additional buttons
@@ -31,24 +60,33 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun AppBarWithAdditionalButtons(
text: String,
+ modifier: Modifier = Modifier,
startButton: AdditionalButton? = null,
endButton: AdditionalButton? = null,
) {
Box(
- modifier = Modifier
+ modifier = modifier
.fillMaxWidth()
- .heightIn(TangemTheme.dimens.size56)
.padding(all = TangemTheme.dimens.spacing16),
) {
if (startButton != null) {
- IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) {
- Icon(
- painter = painterResource(id = startButton.iconRes),
- contentDescription = null,
- modifier = Modifier.size(TangemTheme.dimens.size24),
- tint = TangemTheme.colors.icon.primary1,
- )
- }
+ Icon(
+ painter = painterResource(id = startButton.iconRes),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.primary1,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size24)
+ .align(Alignment.CenterStart)
+ .clickable(
+ onClick = startButton.onIconClicked,
+ role = Role.Button,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = androidx.compose.material.ripple.rememberRipple(
+ bounded = false,
+ radius = TangemTheme.dimens.size24 / 2,
+ ),
+ ),
+ )
}
Text(
@@ -60,14 +98,23 @@ fun AppBarWithAdditionalButtons(
)
if (endButton != null) {
- IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) {
- Icon(
- painter = painterResource(id = endButton.iconRes),
- contentDescription = null,
- modifier = Modifier.size(TangemTheme.dimens.size24),
- tint = TangemTheme.colors.icon.primary1,
- )
- }
+ Icon(
+ painter = painterResource(id = endButton.iconRes),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.primary1,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size24)
+ .align(Alignment.CenterEnd)
+ .clickable(
+ onClick = endButton.onIconClicked,
+ role = Role.Button,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = androidx.compose.material.ripple.rememberRipple(
+ bounded = false,
+ radius = TangemTheme.dimens.size24 / 2,
+ ),
+ ),
+ )
}
}
}
@@ -87,6 +134,7 @@ private fun Preview_AppBarWithAdditionalButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@@ -102,6 +150,7 @@ private fun Preview_AppBarWithOnlyStartButtons() {
iconRes = R.drawable.ic_scan_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
@@ -117,6 +166,7 @@ private fun Preview_AppBarWithOnlyEndButtons() {
iconRes = R.drawable.ic_more_vertical_24,
onIconClicked = {},
),
+ modifier = Modifier.background(TangemTheme.colors.background.secondary),
)
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
index 55321fa0b2..863f3e59cc 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
@@ -83,6 +83,7 @@ data class TangemDimens internal constructor(
val size96: Dp = 96.dp,
val size102: Dp = 102.dp,
val size108: Dp = 108.dp,
+ val size110: Dp = 110.dp,
val size116: Dp = 116.dp,
val size120: Dp = 120.dp,
val size142: Dp = 142.dp,
diff --git a/features/disclaimer/api/.gitignore b/features/disclaimer/api/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/disclaimer/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/disclaimer/api/build.gradle.kts b/features/disclaimer/api/build.gradle.kts
new file mode 100644
index 0000000000..fcd117da1b
--- /dev/null
+++ b/features/disclaimer/api/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ id("configuration")
+}
+
+android {
+ namespace = "com.tangem.features.disclaimer.api"
+}
+
+dependencies {
+
+ /** AndroidX */
+ implementation(deps.androidx.fragment.ktx)
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..5575d38b51
--- /dev/null
+++ b/features/disclaimer/api/src/main/java/com/tangem/features/disclaimer/api/DisclaimerRouter.kt
@@ -0,0 +1,12 @@
+package com.tangem.features.disclaimer.api
+
+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/.gitignore b/features/disclaimer/impl/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/disclaimer/impl/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/disclaimer/impl/build.gradle.kts b/features/disclaimer/impl/build.gradle.kts
new file mode 100644
index 0000000000..bc658e93f3
--- /dev/null
+++ b/features/disclaimer/impl/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ alias(deps.plugins.kotlin.kapt)
+ alias(deps.plugins.hilt.android)
+ id("configuration")
+}
+
+android {
+ namespace = "com.tangem.features.disclaimer.impl"
+}
+
+dependencies {
+ /** AndroidX */
+ implementation(deps.androidx.fragment.ktx)
+ implementation(deps.androidx.appCompat)
+ implementation(deps.lifecycle.compose)
+ implementation(deps.androidx.activity.compose)
+
+ /** Compose */
+ implementation(deps.compose.foundation)
+ implementation(deps.compose.ui)
+ implementation(deps.compose.ui.tooling)
+ implementation(deps.compose.accompanist.systemUiController)
+ implementation(deps.compose.accompanist.permission)
+ implementation(deps.compose.material3)
+
+ /** Core modules */
+ implementation(projects.core.ui)
+ implementation(projects.core.utils)
+ implementation(projects.core.featuretoggles)
+ implementation(projects.core.navigation)
+
+ /** Domain modules */
+ implementation(projects.domain.models)
+ implementation(projects.domain.card)
+
+ /** Feature modules */
+ implementation(projects.features.disclaimer.api)
+
+ /** DI */
+ implementation(deps.hilt.android)
+ implementation(project(":domain:settings"))
+ kapt(deps.hilt.kapt)
+}
\ 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
new file mode 100644
index 0000000000..e0a30ce395
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/DisclaimerFragment.kt
@@ -0,0 +1,58 @@
+package com.tangem.features.disclaimer.impl
+
+import android.os.Bundle
+import androidx.activity.compose.BackHandler
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.fragment.app.viewModels
+import com.tangem.core.navigation.ReduxNavController
+import com.tangem.core.ui.UiDependencies
+import com.tangem.core.ui.components.SystemBarsEffect
+import com.tangem.core.ui.res.TangemTheme
+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
+import javax.inject.Inject
+
+@AndroidEntryPoint
+internal class DisclaimerFragment : ComposeFragment() {
+
+ @Inject
+ override lateinit var uiDependencies: UiDependencies
+
+ @Inject
+ lateinit var reduxNavController: ReduxNavController
+
+ private val viewModel by viewModels()
+
+ private val isTosAccepted: Boolean
+ get() = arguments?.getBoolean(DisclaimerRouter.IS_TOS_ACCEPTED_KEY) ?: false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ lifecycle.addObserver(viewModel)
+ }
+
+ @Composable
+ override fun ScreenContent(modifier: Modifier) {
+ val systemBarsColor = TangemTheme.colors.background.primary
+ BackHandler {
+ if (isTosAccepted) {
+ reduxNavController.popBackStack()
+ } else {
+ requireActivity().finish()
+ }
+ }
+ SystemBarsEffect {
+ setSystemBarsColor(systemBarsColor)
+ }
+ DisclaimerScreen(viewModel.state, reduxNavController::popBackStack)
+ }
+
+ companion object {
+ /** Create disclaimer fragment instance */
+ fun create(): DisclaimerFragment = DisclaimerFragment()
+ }
+}
\ No newline at end of file
diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/DisclaimerRouterModule.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/DisclaimerRouterModule.kt
new file mode 100644
index 0000000000..8b3d34e029
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/di/DisclaimerRouterModule.kt
@@ -0,0 +1,24 @@
+package com.tangem.features.disclaimer.impl.di
+
+import com.tangem.core.navigation.ReduxNavController
+import com.tangem.features.disclaimer.api.DisclaimerRouter
+import com.tangem.features.disclaimer.impl.navigation.DefaultDisclaimerRouter
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.scopes.ActivityScoped
+
+/**
+ * DI module provides implementation of [DisclaimerRouter]
+ */
+@Module
+@InstallIn(ActivityComponent::class)
+object DisclaimerRouterModule {
+
+ @Provides
+ @ActivityScoped
+ fun provideDisclaimerRouter(reduxNavController: ReduxNavController): DisclaimerRouter {
+ return DefaultDisclaimerRouter(reduxNavController)
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..4860d3265d
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/DefaultDisclaimerRouter.kt
@@ -0,0 +1,23 @@
+package com.tangem.features.disclaimer.impl.navigation
+
+import androidx.fragment.app.Fragment
+import com.tangem.core.navigation.AppScreen
+import com.tangem.core.navigation.NavigationAction
+import com.tangem.core.navigation.ReduxNavController
+import com.tangem.features.disclaimer.impl.DisclaimerFragment
+import javax.inject.Inject
+
+internal class DefaultDisclaimerRouter @Inject constructor(
+ private val reduxNavController: ReduxNavController,
+) : InnerDisclaimerRouter {
+
+ override fun entryFragment(): Fragment = DisclaimerFragment.create()
+
+ override fun openPushNotificationPermission() {
+ // todo [REDACTED_JIRA]
+ }
+
+ override fun openHome() {
+ reduxNavController.navigate(action = NavigationAction.NavigateTo(screen = AppScreen.Home))
+ }
+}
\ No newline at end of file
diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/InnerDisclaimerRouter.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/InnerDisclaimerRouter.kt
new file mode 100644
index 0000000000..5077db0901
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/navigation/InnerDisclaimerRouter.kt
@@ -0,0 +1,10 @@
+package com.tangem.features.disclaimer.impl.navigation
+
+import com.tangem.features.disclaimer.api.DisclaimerRouter
+
+internal interface InnerDisclaimerRouter : DisclaimerRouter {
+
+ fun openPushNotificationPermission()
+
+ fun openHome()
+}
\ No newline at end of file
diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/state/DisclaimerState.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/state/DisclaimerState.kt
new file mode 100644
index 0000000000..01d0a398a6
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/state/DisclaimerState.kt
@@ -0,0 +1,16 @@
+package com.tangem.features.disclaimer.impl.presentation.state
+
+internal data class DisclaimerState(
+ val url: String,
+ val isTosAccepted: Boolean,
+ val onAccept: (Boolean) -> Unit,
+)
+
+internal object DummyDisclaimer {
+
+ val state = DisclaimerState(
+ url = "https://tangem.com/tangem_tos.html",
+ isTosAccepted = false,
+ onAccept = {},
+ )
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..f99bd7bc4e
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerScreen.kt
@@ -0,0 +1,174 @@
+package com.tangem.features.disclaimer.impl.presentation.ui
+
+import android.Manifest
+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.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+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.extensions.resourceReference
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+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
+
+@Composable
+internal fun DisclaimerScreen(state: DisclaimerState, onBackClick: () -> Unit) {
+ val bottomPadding = if (state.isTosAccepted) TangemTheme.dimens.spacing16 else TangemTheme.dimens.size64
+ Box(
+ modifier = Modifier
+ .statusBarsPadding()
+ .background(TangemColorPalette.Dark6),
+ ) {
+ Column(
+ modifier = Modifier
+ .navigationBarsPadding()
+ .padding(bottom = bottomPadding),
+ ) {
+ AppBarWithAdditionalButtons(
+ text = resourceReference(R.string.disclaimer_title),
+ startButton = AdditionalButton(
+ iconRes = R.drawable.ic_back_24,
+ onIconClicked = onBackClick,
+ ).takeIf { state.isTosAccepted },
+ )
+ DisclaimerContent(state.url)
+ }
+ if (!state.isTosAccepted) {
+ DisclaimerButton(state.onAccept)
+ }
+ }
+}
+
+@Composable
+private fun ColumnScope.DisclaimerContent(url: String) {
+ val transparent = Color.Transparent
+
+ val progressState = remember { mutableStateOf(ProgressState.Loading) }
+ val webClient = remember { DisclaimerWebViewClient(progressState) }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ ) {
+ AndroidView(
+ factory = {
+ WebView(it).apply {
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ setBackgroundColor(transparent.toArgb())
+ settings.allowFileAccess = false
+ settings.javaScriptEnabled = false
+ overScrollMode = View.OVER_SCROLL_NEVER
+ webViewClient = webClient
+ loadUrl(url)
+ }
+ },
+ )
+
+ when (progressState.value) {
+ ProgressState.Loading -> CircularProgressIndicator(
+ color = TangemTheme.colors.icon.informative,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(TangemTheme.dimens.spacing8),
+ )
+ ProgressState.Error -> 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
+ }
+ }
+}
+
+@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 bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size110 + bottomInsetsPx.dp)
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ TangemTheme.colors.background.primary.copy(alpha = 0f),
+ TangemTheme.colors.background.primary.copy(alpha = 0.75f),
+ TangemTheme.colors.background.primary.copy(alpha = 0.95f),
+ TangemTheme.colors.background.primary,
+ ),
+ ),
+ ),
+ )
+ PrimaryButton(
+ text = stringResource(id = R.string.common_accept),
+ onClick = { onAccept(shouldAskPushPermission) },
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .navigationBarsPadding()
+ .padding(
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ bottom = TangemTheme.dimens.spacing16,
+ )
+ .fillMaxWidth(),
+ )
+}
+
+// region Preview
+@Preview(showBackground = true, widthDp = 360, heightDp = 800)
+@Preview(showBackground = true, widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun DisclaimerScreen_Preview() {
+ TangemThemePreview {
+ DisclaimerScreen(state = DummyDisclaimer.state, onBackClick = {})
+ }
+}
+// endregion
\ No newline at end of file
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
new file mode 100644
index 0000000000..348cad4107
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/ui/DisclaimerWebViewClient.kt
@@ -0,0 +1,62 @@
+package com.tangem.features.disclaimer.impl.presentation.ui
+
+import android.graphics.Bitmap
+import android.webkit.*
+import androidx.compose.runtime.MutableState
+
+internal enum class ProgressState {
+ Loading,
+ Done,
+ Error,
+}
+
+internal class DisclaimerWebViewClient(private val progressState: MutableState) : WebViewClient() {
+
+ private var loadingUrl: String? = null
+ private var loadedUrl: String? = null
+
+ fun reset() {
+ loadingUrl = null
+ loadedUrl = null
+ progressState.value = ProgressState.Loading
+ }
+
+ override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
+ super.onPageStarted(view, url, favicon)
+
+ if (loadingUrl != url) progressState.value = ProgressState.Loading
+ loadingUrl = url
+ }
+
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+
+ if (loadedUrl != url) progressState.value = ProgressState.Done
+ loadedUrl = url
+ }
+
+ override fun onReceivedError(view: WebView?, resourceRequest: WebResourceRequest?, error: WebResourceError?) {
+ super.onReceivedError(view, resourceRequest, error)
+ error?.let { progressState.value = ProgressState.Error }
+ }
+
+ override fun onReceivedHttpError(
+ view: WebView?,
+ resourceRequest: WebResourceRequest?,
+ errorResponse: WebResourceResponse?,
+ ) {
+ super.onReceivedHttpError(view, resourceRequest, errorResponse)
+
+ if (resourceRequest != null && errorResponse != null) {
+ val isDifferentUrl = resourceRequest.url?.toString() != loadingUrl
+ val isSuccessCode = errorResponse.statusCode < RESPONSE_USER_ERROR_STATUS_CODE
+ val isNotDone = progressState.value != ProgressState.Done
+ if (isDifferentUrl || isSuccessCode || isNotDone) return
+ progressState.value = ProgressState.Error
+ }
+ }
+
+ companion object {
+ private const val RESPONSE_USER_ERROR_STATUS_CODE = 400
+ }
+}
\ No newline at end of file
diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerClickIntents.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerClickIntents.kt
new file mode 100644
index 0000000000..d80a1236ed
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerClickIntents.kt
@@ -0,0 +1,6 @@
+package com.tangem.features.disclaimer.impl.presentation.viewmodel
+
+internal interface DisclaimerClickIntents {
+
+ fun onAccept(shouldAskPushPermission: Boolean)
+}
\ No newline at end of file
diff --git a/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerViewModel.kt b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerViewModel.kt
new file mode 100644
index 0000000000..bfc97b16d8
--- /dev/null
+++ b/features/disclaimer/impl/src/main/java/com/tangem/features/disclaimer/impl/presentation/viewmodel/DisclaimerViewModel.kt
@@ -0,0 +1,42 @@
+package com.tangem.features.disclaimer.impl.presentation.viewmodel
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+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
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+internal class DisclaimerViewModel @Inject constructor(
+ private val cardRepository: CardRepository,
+ private val disclaimerRouter: DefaultDisclaimerRouter,
+ savedStateHandle: SavedStateHandle,
+) : ViewModel(), DefaultLifecycleObserver, DisclaimerClickIntents {
+
+ private val isTosAccepted: Boolean = savedStateHandle[IS_TOS_ACCEPTED_KEY] ?: false
+
+ val state: DisclaimerState
+ get() = DisclaimerState(
+ onAccept = ::onAccept,
+ url = DISCLAIMER_URL,
+ isTosAccepted = isTosAccepted,
+ )
+
+ override fun onAccept(shouldAskPushPermission: Boolean) {
+ viewModelScope.launch {
+ cardRepository.acceptTangemTOS()
+ // todo [REDACTED_JIRA]
+ disclaimerRouter.openHome()
+ }
+ }
+
+ private companion object {
+ const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html"
+ }
+}
\ No newline at end of file
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index c4f0245a43..7fb3f2a8f0 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -168,6 +168,7 @@ compose-navigation-hilt = { module = "androidx.hilt:hilt-navigation-compose", ve
compose-accompanist-appCompatTheme = { module = "com.google.accompanist:accompanist-appcompat-theme", version.ref = "compose-accompanist" }
compose-accompanist-systemUiController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "compose-accompanist" }
compose-accompanist-webView = { module = "com.google.accompanist:accompanist-webview", version.ref = "compose-accompanist" }
+compose-accompanist-permission = { module = "com.google.accompanist:accompanist-permissions", version.ref = "compose-accompanist" }
compose-paging = { module = "androidx.paging:paging-compose", version.ref = "compose-paging" }
compose-reorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "compose-reorderable" }
# endregion Compose
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1e8f4f4945..82e7f8bef8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -176,6 +176,9 @@ include(":features:staking:impl")
include(":features:details:api")
include(":features:details:impl")
+
+include(":features:disclaimer:api")
+include(":features:disclaimer:impl")
// endregion Feature modules
// region Domain modules