Updated on 2026-08-14
This commit is contained in:
parent
6446a08369
commit
81362b8ae9
19 changed files with 608 additions and 23 deletions
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
@ -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 <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=50%3A403&t=tRQ4KlzkjV7TCLZl-4">
|
||||
* Figma Component</a>
|
||||
*/
|
||||
@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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
1
features/disclaimer/api/.gitignore
vendored
Normal file
1
features/disclaimer/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
15
features/disclaimer/api/build.gradle.kts
Normal file
15
features/disclaimer/api/build.gradle.kts
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
1
features/disclaimer/impl/.gitignore
vendored
Normal file
1
features/disclaimer/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
45
features/disclaimer/impl/build.gradle.kts
Normal file
45
features/disclaimer/impl/build.gradle.kts
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<DisclaimerViewModel>()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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<ProgressState>) : 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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.disclaimer.impl.presentation.viewmodel
|
||||
|
||||
internal interface DisclaimerClickIntents {
|
||||
|
||||
fun onAccept(shouldAskPushPermission: Boolean)
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue