Updated on 2026-08-14
This commit is contained in:
parent
54f0be3e6c
commit
587a31520d
12 changed files with 274 additions and 53 deletions
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.tap.features.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.essenty.instancekeeper.getOrCreateSimple
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.components.DialogFullScreen
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("UnusedPrivateProperty")
|
||||
class RootDetectedWarningComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
private val securityInfoProvider: DeviceSecurityInfoProvider,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) : AppComponentContext by appComponentContext, ComposableContentComponent {
|
||||
|
||||
private val isShown = instanceKeeper.getOrCreateSimple { MutableStateFlow(false) }
|
||||
|
||||
suspend fun tryToShowWarningAndWaitContinuation() {
|
||||
if (isShown.value) return
|
||||
|
||||
if (settingsRepository.isRootDetectedWarningShown().not() && securityInfoProvider.isSecurityExposed()) {
|
||||
isShown.value = true
|
||||
}
|
||||
|
||||
isShown.first { it == false } // Wait until the warning is dismissed
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val isShownState by isShown.collectAsStateWithLifecycle()
|
||||
|
||||
if (isShownState) {
|
||||
DialogFullScreen(onDismissRequest = {}) {
|
||||
RootDetectedWarningContent(
|
||||
modifier = modifier,
|
||||
onContinueClick = remember(this) { ::onContinueClick },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinueClick() {
|
||||
componentScope.launch {
|
||||
settingsRepository.setRootDetectedWarningShown(true)
|
||||
isShown.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : ComponentFactory<Unit, RootDetectedWarningComponent> {
|
||||
override fun create(context: AppComponentContext, params: Unit): RootDetectedWarningComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.tangem.tap.features.root
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.icons.HighlightedIcon
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun RootDetectedWarningContent(modifier: Modifier = Modifier, onContinueClick: () -> Unit = {}) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
InfoBlock(
|
||||
modifier = Modifier.padding(top = 48.dp, bottom = 24.dp),
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.padding(bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.common_understand_continue),
|
||||
onClick = onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoBlock(modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
HighlightedIcon(
|
||||
icon = R.drawable.ic_alert_circle_24,
|
||||
iconTint = TangemTheme.colors.icon.warning,
|
||||
)
|
||||
|
||||
SpacerH(20.dp)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.root_detected_warning_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(12.dp)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
text = stringResourceSafe(R.string.root_detected_warning_description),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
RootDetectedWarningContent()
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,7 @@ internal fun RootContent(
|
|||
modifier: Modifier = Modifier,
|
||||
wcContent: @Composable (modifier: Modifier) -> Unit,
|
||||
hotAccessCodeContent: @Composable (modifier: Modifier) -> Unit,
|
||||
rootDetectedWarningContent: @Composable (modifier: Modifier) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
@ -82,6 +83,8 @@ internal fun RootContent(
|
|||
|
||||
hotAccessCodeContent(Modifier.fillMaxSize())
|
||||
|
||||
rootDetectedWarningContent(Modifier.fillMaxSize())
|
||||
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import com.tangem.tap.common.SnackbarHandler
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.hot.TangemHotSDKProxy
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.root.RootDetectedWarningComponent
|
||||
import com.tangem.tap.routing.RootContent
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
|
|
@ -64,6 +65,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
private val tangemHotSDKProxy: TangemHotSDKProxy,
|
||||
private val hotAccessCodeRequestComponentFactory: HotAccessCodeRequestComponent.Factory,
|
||||
private val hotAccessCodeRequesterProxy: HotWalletPasswordRequesterProxy,
|
||||
private val rootDetectedWarningComponentFactory: RootDetectedWarningComponent.Factory,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val cardRepository: CardRepository,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
|
|
@ -85,6 +87,11 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
.create(child("hotAccessCodeRequestComponent"), Unit)
|
||||
}
|
||||
|
||||
private val rootDetectedWarningComponent: RootDetectedWarningComponent by lazy {
|
||||
rootDetectedWarningComponentFactory
|
||||
.create(child("rootDetectedWarningComponent"), Unit)
|
||||
}
|
||||
|
||||
private val navigation = navigationProvider.getOrCreateTyped<AppRoute>()
|
||||
|
||||
private val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
||||
|
|
@ -134,6 +141,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
private fun initializeInitialNavigation() {
|
||||
if (initialStack.isNullOrEmpty()) {
|
||||
componentScope.launch {
|
||||
rootDetectedWarningComponent.tryToShowWarningAndWaitContinuation()
|
||||
val initialRoute = resolveInitialRoute()
|
||||
router.replaceAll(initialRoute)
|
||||
}
|
||||
|
|
@ -177,6 +185,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
modifier = modifier,
|
||||
wcContent = { wcRoutingComponent.Content(it) },
|
||||
hotAccessCodeContent = { hotAccessCodeRequestComponent.Content(it) },
|
||||
rootDetectedWarningContent = { rootDetectedWarningComponent.Content(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue