Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-03 10:30:14 +05:00
commit d2b6e547db
22 changed files with 173 additions and 193 deletions

View file

@ -49,7 +49,6 @@ 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.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
@ -175,9 +174,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
internal lateinit var routingComponentFactory: RoutingComponent.Factory
@Inject
lateinit var disclaimerRouter: DisclaimerRouter
@Inject
lateinit var pushNotificationsRouter: PushNotificationsRouter
@ -298,7 +294,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
qrScanningRouter = qrScanningRouter,
emailSender = emailSender,
stakingRouter = stakingRouter,
disclaimerRouter = disclaimerRouter,
pushNotificationsRouter = pushNotificationsRouter,
),
)

View file

@ -4,7 +4,6 @@ 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.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
@ -27,7 +26,6 @@ sealed interface DaggerGraphAction : Action {
val qrScanningRouter: QrScanningRouter,
val emailSender: EmailSender,
val stakingRouter: StakingRouter,
val disclaimerRouter: DisclaimerRouter,
val pushNotificationsRouter: PushNotificationsRouter,
) : DaggerGraphAction
}

View file

@ -23,7 +23,6 @@ object DaggerGraphReducer {
qrScanningRouter = action.qrScanningRouter,
emailSender = action.emailSender,
stakingRouter = action.stakingRouter,
disclaimerRouter = action.disclaimerRouter,
pushNotificationsRouter = action.pushNotificationsRouter,
)
}

View file

@ -30,7 +30,6 @@ 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.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -88,7 +87,6 @@ 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

View file

@ -8,7 +8,7 @@ import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.DisclaimerRouter
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
@ -46,6 +46,7 @@ import javax.inject.Inject
internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory,
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
@ -55,7 +56,6 @@ internal class ChildFactory @Inject constructor(
private val detailsFeatureToggles: DetailsFeatureToggles,
private val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles,
private val pushNotificationRouter: PushNotificationsRouter,
private val disclaimerRouter: DisclaimerRouter,
) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -106,7 +106,11 @@ internal class ChildFactory @Inject constructor(
}
is AppRoute.Disclaimer -> {
if (pushNotificationsFeatureToggles.isPushNotificationsEnabled) {
route.asFragmentChild(Provider { disclaimerRouter.entryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = DisclaimerComponent.Params(route.isTosAccepted),
componentFactory = disclaimerComponentFactory,
)
} else {
route.asFragmentChild(Provider { DisclaimerFragment() })
}

View file

@ -10,6 +10,7 @@ android {
dependencies {
/** AndroidX */
implementation(deps.androidx.fragment.ktx)
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
}

View file

@ -1,8 +0,0 @@
package com.tangem.features.disclaimer.api
import androidx.fragment.app.Fragment
interface DisclaimerRouter {
fun entryFragment(): Fragment
}

View file

@ -0,0 +1,12 @@
package com.tangem.features.disclaimer.api.components
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface DisclaimerComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Params, DisclaimerComponent>
data class Params(
val isTosAccepted: Boolean,
)
}

View file

@ -11,9 +11,7 @@ android {
}
dependencies {
/** AndroidX */
implementation(deps.androidx.fragment.ktx)
implementation(deps.androidx.appCompat)
/* AndroidX */
implementation(deps.lifecycle.compose)
implementation(deps.androidx.activity.compose)
@ -31,6 +29,7 @@ dependencies {
implementation(projects.core.utils)
implementation(projects.core.featuretoggles)
implementation(projects.core.navigation)
implementation(projects.core.decompose)
implementation(projects.common.routing)
/** Domain modules */

View file

@ -1,52 +0,0 @@
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.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.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 appRouter: AppRouter
private val viewModel by viewModels<DisclaimerViewModel>()
private val isTosAccepted: Boolean
get() = arguments?.getBoolean(IS_TOS_ACCEPTED_KEY) ?: false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(viewModel)
}
@Composable
override fun ScreenContent(modifier: Modifier) {
BackHandler {
if (isTosAccepted) {
appRouter.pop()
} else {
requireActivity().finish()
}
}
DisclaimerScreen(viewModel.state, appRouter::pop)
}
companion object {
/** Create disclaimer fragment instance */
fun create(): DisclaimerFragment = DisclaimerFragment()
}
}

View file

@ -0,0 +1,48 @@
package com.tangem.features.disclaimer.impl.component.impl
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.disclaimer.impl.model.DisclaimerModel
import com.tangem.features.disclaimer.impl.ui.DisclaimerScreen
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultDisclaimerComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: DisclaimerComponent.Params,
private val appFinisher: AppFinisher,
) : DisclaimerComponent, AppComponentContext by context {
private val model: DisclaimerModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
BackHandler {
if (params.isTosAccepted) {
state.popBack()
} else {
appFinisher.finish()
}
}
DisclaimerScreen(state = state)
}
@AssistedFactory
interface Factory : DisclaimerComponent.Factory {
override fun create(
context: AppComponentContext,
params: DisclaimerComponent.Params,
): DefaultDisclaimerComponent
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.disclaimer.impl.di
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.disclaimer.impl.component.impl.DefaultDisclaimerComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindDisclaimerComponentFactory(factory: DefaultDisclaimerComponent.Factory): DisclaimerComponent.Factory
}

View file

@ -1,24 +0,0 @@
package com.tangem.features.disclaimer.impl.di
import com.tangem.common.routing.AppRouter
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(appRouter: AppRouter): DisclaimerRouter {
return DefaultDisclaimerRouter(appRouter)
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.disclaimer.impl.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.disclaimer.impl.model.DisclaimerModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(DisclaimerModel::class)
fun provideDisclaimerModel(model: DisclaimerModel): Model
}

View file

@ -1,16 +1,18 @@
package com.tangem.features.disclaimer.impl.presentation.state
package com.tangem.features.disclaimer.impl.entity
internal data class DisclaimerState(
internal data class DisclaimerUM(
val url: String,
val isTosAccepted: Boolean,
val onAccept: (Boolean) -> Unit,
val popBack: () -> Unit,
)
internal object DummyDisclaimer {
val state = DisclaimerState(
val state = DisclaimerUM(
url = "https://tangem.com/tangem_tos.html",
isTosAccepted = false,
onAccept = {},
popBack = {},
)
}

View file

@ -0,0 +1,49 @@
package com.tangem.features.disclaimer.impl.model
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.card.repository.CardRepository
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
internal class DisclaimerModel @Inject constructor(
private val cardRepository: CardRepository,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
) : Model() {
private val params: DisclaimerComponent.Params = paramsContainer.require()
val state: MutableStateFlow<DisclaimerUM> = MutableStateFlow(
DisclaimerUM(
onAccept = ::onAccept,
url = DISCLAIMER_URL,
isTosAccepted = params.isTosAccepted,
popBack = router::pop,
),
)
private fun onAccept(shouldAskPushPermission: Boolean) {
modelScope.launch {
cardRepository.acceptTangemTOS()
if (shouldAskPushPermission) {
router.push(AppRoute.PushNotification)
} else {
router.push(AppRoute.Home)
}
}
}
private companion object {
const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html"
}
}

View file

@ -1,22 +0,0 @@
package com.tangem.features.disclaimer.impl.navigation
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 @Inject constructor(
private val appRouter: AppRouter,
) : InnerDisclaimerRouter {
override fun entryFragment(): Fragment = DisclaimerFragment.create()
override fun openPushNotificationPermission() {
appRouter.push(AppRoute.PushNotification)
}
override fun openHome() {
appRouter.push(AppRoute.Home)
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.features.disclaimer.impl.navigation
import com.tangem.features.disclaimer.api.DisclaimerRouter
internal interface InnerDisclaimerRouter : DisclaimerRouter {
fun openPushNotificationPermission()
fun openHome()
}

View file

@ -1,6 +0,0 @@
package com.tangem.features.disclaimer.impl.presentation.viewmodel
internal interface DisclaimerClickIntents {
fun onAccept(shouldAskPushPermission: Boolean)
}

View file

@ -1,41 +0,0 @@
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.common.routing.AppRoute.Disclaimer.Companion.IS_TOS_ACCEPTED_KEY
import com.tangem.domain.card.repository.CardRepository
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()
disclaimerRouter.openPushNotificationPermission()
}
}
private companion object {
const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html"
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.disclaimer.impl.presentation.ui
package com.tangem.features.disclaimer.impl.ui
import android.annotation.SuppressLint
import android.content.res.Configuration
@ -33,12 +33,12 @@ 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
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
import com.tangem.features.disclaimer.impl.entity.DummyDisclaimer
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
@Composable
internal fun DisclaimerScreen(state: DisclaimerState, onBackClick: () -> Unit) {
internal fun DisclaimerScreen(state: DisclaimerUM) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val bottomPadding = if (state.isTosAccepted) {
bottomBarHeight + TangemTheme.dimens.size16
@ -61,7 +61,7 @@ internal fun DisclaimerScreen(state: DisclaimerState, onBackClick: () -> Unit) {
text = resourceReference(R.string.disclaimer_title),
startButton = AdditionalButton(
iconRes = R.drawable.ic_back_24,
onIconClicked = onBackClick,
onIconClicked = state.popBack,
).takeIf { state.isTosAccepted },
textColor = textColor,
iconColor = iconColor,
@ -134,12 +134,12 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) {
@OptIn(ExperimentalPermissionsApi::class)
@Composable
private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
val shouldAskPushPermission = getPushPermissionOrNull()?.let { permission ->
val isPermissionGranted = getPushPermissionOrNull()?.let { permission ->
rememberPermissionState(permission = permission).status.isGranted
} ?: true
PrimaryButton(
text = stringResource(id = R.string.common_accept),
onClick = { onAccept(shouldAskPushPermission) },
onClick = { onAccept(!isPermissionGranted) },
colors = TangemButtonColors(
backgroundColor = TangemColorPalette.Light4,
contentColor = TangemColorPalette.Dark6,
@ -164,7 +164,7 @@ private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
@Composable
private fun DisclaimerScreen_Preview() {
TangemThemePreview {
DisclaimerScreen(state = DummyDisclaimer.state, onBackClick = {})
DisclaimerScreen(state = DummyDisclaimer.state)
}
}
// endregion

View file

@ -1,4 +1,4 @@
package com.tangem.features.disclaimer.impl.presentation.ui
package com.tangem.features.disclaimer.impl.ui
import android.graphics.Bitmap
import android.webkit.*