Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-17 15:16:22 +05:00
parent 64c7c64b3a
commit 713cad5fd7
26 changed files with 100 additions and 359 deletions

View file

@ -26,10 +26,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.deeplink.DeeplinkConst.WEBLINK_KEY
import com.tangem.common.routing.deeplink.PayloadToDeeplinkConverter
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.di.RootAppComponentContext
@ -41,7 +39,6 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
@ -62,11 +59,9 @@ import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.ActivityResultCallbackHolder
import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.analytics.events.Push
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.routing.component.RoutingComponent
@ -172,12 +167,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
@Inject
internal lateinit var testerMenuLauncher: TesterMenuLauncher
@Inject
internal lateinit var intentProcessor: IntentProcessor
@Inject
internal lateinit var onPushClickedIntentHandler: OnPushClickedIntentHandler
@Inject
internal lateinit var backgroundScanIntentHandler: BackgroundScanIntentHandler
@ -248,6 +237,10 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
if (BuildConfig.TESTER_MENU_ENABLED) {
lifecycle.addObserver(testerMenuLauncher.launchOnKeyEventObserver)
}
if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
}
private fun setRootContent() {
@ -257,6 +250,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
val routingComponent = routingComponentFactory.create(
context = rootComponentContext,
initialStack = appRouterConfig.stack,
launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intent),
)
setContent {
@ -281,8 +275,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
clearAllHotWalletContextualUnlockUseCase = clearAllHotWalletContextualUnlockUseCase,
)
initIntentHandlers()
store.dispatch(
DaggerGraphAction.SetActivityDependencies(
scanCardUseCase = scanCardUseCase,
@ -339,18 +331,12 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
dialogManager.onStart(this)
}
override fun onResume() {
super.onResume()
navigateToInitialScreenIfNeeded(intent)
}
override fun onStop() {
dialogManager.onStop()
super.onStop()
}
override fun onDestroy() {
intentProcessor.removeAll()
// workaround: kill process when activity destroy to avoid state when lock() wallets
// and navigation to unlock screen was skipped because system kills activity but not process
if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) {
@ -359,10 +345,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
super.onDestroy()
}
private fun initIntentHandlers() {
intentProcessor.addHandler(onPushClickedIntentHandler)
}
private fun updateAppTheme(appThemeMode: AppThemeMode) {
val mode = when (appThemeMode) {
AppThemeMode.FORCE_DARK -> AppCompatDelegate.MODE_NIGHT_YES
@ -388,8 +370,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
lifecycleScope.launch {
intentProcessor.handleIntent(intent = intent, isFromForeground = true)
val fromPush = intent?.extras?.containsKey(OPENED_FROM_GCM_PUSH) ?: false
if (fromPush) {
analyticsEventsHandler.send(Push.PushNotificationOpened)
}
if (intent != null) {
@ -431,134 +414,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
}
}
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
// TODO refactor this method to return a route instead of navigating directly
if (hotWalletFeatureToggles.isHotWalletEnabled) {
navigateToInitialScreenIfNeededNew(intentWhichStartedActivity)
return
}
val backStack = appRouterConfig.stack ?: emptyList()
// TODO move inital navigation to navigation component ([REDACTED_JIRA])
val isOnlyInitialRoute = backStack.all { it is AppRoute.Initial }
val isOnInitialScreen = backStack.all { it is AppRoute.Welcome || it is AppRoute.Home }
val isNotScannedBefore = store.state.globalState.scanResponse == null
val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted
when {
!isOnInitialScreen && isNotScannedBefore && isOnboardingServiceNotActive -> {
navigateToInitialScreen(intentWhichStartedActivity)
}
backStack.isEmpty() -> {
navigateToInitialScreen(intentWhichStartedActivity)
}
isOnlyInitialRoute -> navigateToInitialScreen(intentWhichStartedActivity)
else -> Unit
}
}
@Deprecated("Refactor this method to return a route instead of navigating directly")
private fun navigateToInitialScreenIfNeededNew(intentWhichStartedActivity: Intent?) {
lifecycleScope.launch {
val userWallets = userWalletsListRepository.userWalletsSync()
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
if (userWallets.isEmpty()) {
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
val route = if (shouldShowTos) {
AppRoute.Disclaimer(isTosAccepted = false)
} else {
AppRoute.Home(launchMode = launchMode)
}
store.dispatchNavigationAction { replaceAll(route) }
intentProcessor.handleIntent(
intent = intentWhichStartedActivity,
isFromForeground = false,
skipNavigationHandlers = false,
)
} else {
if (userWallets.any { it.isLocked }) {
store.dispatchNavigationAction {
replaceAll(
AppRoute.Welcome(
launchMode = launchMode,
intent = intentWhichStartedActivity?.let(::SerializableIntent),
),
)
}
} else {
store.dispatchNavigationAction {
replaceAll(AppRoute.Wallet)
}
}
intentProcessor.handleIntent(
intent = intentWhichStartedActivity,
isFromForeground = false,
skipNavigationHandlers = true,
)
}
if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
viewModel.checkForUnfinishedBackup()
}
}
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
val launchMode = backgroundScanIntentHandler.getInitScreenLaunchMode(intentWhichStartedActivity)
// Workaround to navigate to TangemPayDetails screen. Will be deleted in next PRs
if (tangemPayFeatureToggles.isTangemPayEnabled) {
lifecycleScope.launch {
val selectedUserWalledId = userWalletsListRepository.selectedUserWalletSync()?.walletId
store.dispatchNavigationAction {
replaceAll(AppRoute.TangemPayDetails(requireNotNull(selectedUserWalledId)))
}
}
} else if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
store.dispatchNavigationAction {
replaceAll(
AppRoute.Welcome(
launchMode = launchMode,
intent = intentWhichStartedActivity?.let(::SerializableIntent),
),
)
}
intentProcessor.handleIntent(
intent = intentWhichStartedActivity,
isFromForeground = false,
skipNavigationHandlers = true,
)
} else {
lifecycleScope.launch {
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
val route = if (shouldShowTos) {
AppRoute.Disclaimer(isTosAccepted = false)
} else {
AppRoute.Home(launchMode = launchMode)
}
store.dispatchNavigationAction { replaceAll(route) }
intentProcessor.handleIntent(
intent = intentWhichStartedActivity,
isFromForeground = false,
skipNavigationHandlers = false,
)
}
}
if (intent != null) {
handleDeepLink(intent = intent, isFromOnNewIntent = false)
}
viewModel.checkForUnfinishedBackup()
}
private fun handleDeepLink(intent: Intent, isFromOnNewIntent: Boolean) {
val deepLinkExtras = PayloadToDeeplinkConverter.convertBundle(intent.extras)?.toUri()
val webLink = intent.getStringExtra(WEBLINK_KEY)
@ -627,5 +482,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
companion object {
private const val APP_THEME_LOAD_TIMEOUT = 2
private const val MOCKED_BUILD_TYPE = "mocked"
private const val OPENED_FROM_GCM_PUSH = "google.sent_time" // every bundle from FCM contains this key
}
}

View file

@ -16,7 +16,6 @@ import coil.request.ImageRequest
import com.tangem.domain.common.LogConfig
import com.tangem.tap.MainActivity
import com.tangem.tap.common.images.createCoilImageLoader
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import com.tangem.wallet.R
class PushNotificationDelegate(private val context: Context) {
@ -35,7 +34,7 @@ class PushNotificationDelegate(private val context: Context) {
dataMap.forEach { (key, value) ->
putExtra(key, value)
}
putExtra(OnPushClickedIntentHandler.OPENED_FROM_GCM_PUSH, true)
putExtra(OPENED_FROM_GCM_PUSH, true)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
@ -98,5 +97,6 @@ class PushNotificationDelegate(private val context: Context) {
private companion object {
const val PUSH_NOTIFICATION_REQUEST_CODE = 123
private const val OPENED_FROM_GCM_PUSH = "google.sent_time" // every bundle from FCM contains this key
}
}

View file

@ -1,9 +1,6 @@
package com.tangem.tap.di
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.features.intentHandler.IntentProcessor
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,13 +14,4 @@ internal object IntentHandlingModule {
@Provides
@Singleton
fun provideBackgroundScanIntentHandler(): BackgroundScanIntentHandler = BackgroundScanIntentHandler()
@Provides
@Singleton
fun provideOnPushClickedIntentHandler(analyticsEventHandler: AnalyticsEventHandler): OnPushClickedIntentHandler =
OnPushClickedIntentHandler(analyticsEventHandler)
@Provides
@Singleton
fun provideIntentProcessor(): IntentProcessor = IntentProcessor()
}

View file

@ -7,5 +7,5 @@ import android.content.Intent
*/
interface IntentHandler {
fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean
fun handleIntent(intent: Intent?): Boolean
}

View file

@ -1,27 +0,0 @@
package com.tangem.tap.features.intentHandler
import android.content.Intent
import java.util.concurrent.CopyOnWriteArrayList
/**
[REDACTED_AUTHOR]
*/
// TODO: fixme: close it with the combined interfaces IntentHandler and IntentHandlerHolder
class IntentProcessor {
private val intentHandlers = CopyOnWriteArrayList<IntentHandler>()
fun addHandler(handler: IntentHandler) {
intentHandlers.add(handler)
}
fun removeAll() {
intentHandlers.clear()
}
fun handleIntent(intent: Intent?, isFromForeground: Boolean, skipNavigationHandlers: Boolean = false) {
intentHandlers
.filterNot { handler -> skipNavigationHandlers && handler is AffectsNavigation }
.forEach { handler -> handler.handleIntent(intent, isFromForeground) }
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.tap.features.intentHandler.handlers
import android.content.Intent
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.tap.common.analytics.events.Push
import com.tangem.tap.features.intentHandler.IntentHandler
internal class OnPushClickedIntentHandler(val analyticsEventHandler: AnalyticsEventHandler) : IntentHandler {
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
val fromPush = intent?.extras?.containsKey(OPENED_FROM_GCM_PUSH) ?: false
return if (fromPush) {
analyticsEventHandler.send(Push.PushNotificationOpened)
true
} else {
false
}
}
companion object {
const val OPENED_FROM_GCM_PUSH = "google.sent_time" // every bundle from FCM contains this key
}
}

View file

@ -28,7 +28,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.onramp.FetchHotCryptoUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
@ -43,12 +42,10 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
import com.tangem.feature.swap.analytics.StoriesEvents
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.lifecycle.HiltViewModel
@ -78,7 +75,6 @@ internal class MainViewModel @Inject constructor(
private val getStoryContentUseCase: GetStoryContentUseCase,
private val imagePreloader: ImagePreloader,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val onboardingRepository: OnboardingRepository,
private val getApplicationIdUseCase: GetApplicationIdUseCase,
private val subscribeOnWalletsUseCase: GetSavedWalletsCountUseCase,
private val associateWalletsWithApplicationIdUseCase: AssociateWalletsWithApplicationIdUseCase,
@ -89,6 +85,7 @@ internal class MainViewModel @Inject constructor(
private val appStateHolder: AppStateHolder,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val appRouterConfig: AppRouterConfig,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel() {
@ -139,13 +136,6 @@ internal class MainViewModel @Inject constructor(
multiQuoteUpdater.unsubscribe()
}
fun checkForUnfinishedBackup() {
viewModelScope.launch(dispatchers.main) {
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
}
}
/** Loading the resources needed to run the application */
private fun loadApplicationResources() {
viewModelScope.launch {
@ -159,6 +149,9 @@ internal class MainViewModel @Inject constructor(
prepareSelectedWalletFeedback()
// await while initial route stack is initialized
appRouterConfig.isInitialized.first { it }
isSplashScreenShown = false
}
}

View file

@ -49,7 +49,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
is BackupAction.ResumeFoundUnfinishedBackup -> {
if (action.unfinishedBackupScanResponse != null) {
store.dispatchNavigationAction {
push(
replaceAll(
AppRoute.Onboarding(
scanResponse = action.unfinishedBackupScanResponse,
mode = AppRoute.Onboarding.Mode.ContinueFinalize,

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.welcome.component
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -9,7 +8,6 @@ interface WelcomeComponent : ComposableContentComponent {
data class Params(
val launchMode: InitScreenLaunchMode,
val intent: SerializableIntent?,
)
interface Factory : ComponentFactory<Params, WelcomeComponent>

View file

@ -47,7 +47,7 @@ internal class WelcomeModel @Inject constructor(
val welcomeAction = when (params.launchMode) {
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics(params.intent?.toIntent())
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics
}
store.dispatch(welcomeAction)
@ -55,7 +55,7 @@ internal class WelcomeModel @Inject constructor(
private fun unlockWallets() {
Analytics.send(SignIn.ButtonBiometricSignIn())
store.dispatch(WelcomeAction.ProceedWithBiometrics())
store.dispatch(WelcomeAction.ProceedWithBiometrics)
}
private fun scanCard() {

View file

@ -1,12 +1,11 @@
package com.tangem.tap.features.welcome.redux
import android.content.Intent
import com.tangem.common.core.TangemError
import org.rekotlin.Action
internal sealed interface WelcomeAction : Action {
data class ProceedWithBiometrics(val afterUnlockIntent: Intent? = null) : WelcomeAction {
data object ProceedWithBiometrics : WelcomeAction {
object Success : WelcomeAction
data class Error(val error: TangemError) : WelcomeAction
}
@ -17,8 +16,6 @@ internal sealed interface WelcomeAction : Action {
data class ChangeProgress(val showProgress: Boolean) : WelcomeAction
}
data class ProceedWithIntent(val intent: Intent) : WelcomeAction
object CloseError : WelcomeAction
object ClearUserWallets : WelcomeAction

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.welcome.redux
import android.content.Intent
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
@ -41,7 +40,6 @@ internal class WelcomeMiddleware {
private fun handleAction(action: WelcomeAction) {
mainScope.launch {
when (action) {
is WelcomeAction.ProceedWithIntent -> proceedWithIntent(action.intent)
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics()
is WelcomeAction.ProceedWithCard -> proceedWithCard()
is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving()
@ -50,26 +48,7 @@ internal class WelcomeMiddleware {
}
}
private suspend fun proceedWithIntent(initialIntent: Intent) {
Timber.d(
"""
Proceeding with intent
|- Intent: $initialIntent
""".trimIndent(),
)
val hasUncompletedBackup = backupService.hasIncompletedBackup
if (!hasUncompletedBackup) {
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics(initialIntent))
} else {
store.dispatchWithMain(WelcomeAction.ProceedWithCard)
}
}
private suspend fun proceedWithBiometrics() {
Timber.d("Proceeding with biometry")
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
userWalletsListManager.unlockIfLockable(type = UnlockType.ANY)
.doOnFailure { error ->
@ -89,8 +68,6 @@ internal class WelcomeMiddleware {
}
private suspend fun proceedWithCard() {
Timber.d("Proceeding with card")
scanCardInternal { scanResponse ->
val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse)

View file

@ -14,7 +14,6 @@ internal object WelcomeReducer {
private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState {
return when (action) {
is WelcomeAction.ProceedWithIntent -> state.copy(intent = action.intent)
is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true)
is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true)
is WelcomeAction.ProceedWithBiometrics.Error -> state.copy(

View file

@ -1,12 +1,10 @@
package com.tangem.tap.features.welcome.redux
import android.content.Intent
import com.tangem.common.core.TangemError
import org.rekotlin.StateType
data class WelcomeState(
val isUnlockWithBiometricsInProgress: Boolean = false,
val isUnlockWithCardInProgress: Boolean = false,
val intent: Intent? = null,
val error: TangemError? = null,
) : StateType

View file

@ -3,6 +3,7 @@ package com.tangem.tap.routing.component
import android.content.Intent
import androidx.compose.runtime.Immutable
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -22,6 +23,10 @@ internal interface RoutingComponent : ComposableContentComponent {
}
interface Factory {
fun create(context: AppComponentContext, initialStack: List<AppRoute>?): RoutingComponent
fun create(
context: AppComponentContext,
initialStack: List<AppRoute>?,
launchMode: InitScreenLaunchMode,
): RoutingComponent
}
}

View file

@ -9,6 +9,7 @@ import com.arkivanov.decompose.value.subscribe
import com.arkivanov.essenty.lifecycle.subscribe
import com.google.android.material.snackbar.Snackbar
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
@ -17,27 +18,36 @@ import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.core.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
import com.tangem.features.walletconnect.components.WcRoutingComponent
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.android.create
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.routing.RootContent
import com.tangem.tap.routing.component.RoutingComponent
import com.tangem.tap.routing.component.RoutingComponent.Child
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.utils.ChildFactory
import com.tangem.tap.routing.utils.DeepLinkFactory
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
internal class DefaultRoutingComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted val initialStack: List<AppRoute>?,
@Assisted val launchMode: InitScreenLaunchMode,
private val childFactory: ChildFactory,
private val appRouterConfig: AppRouterConfig,
private val uiDependencies: UiDependencies,
@ -46,6 +56,9 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
private val tangemHotSDKProxy: TangemHotSDKProxy,
private val hotAccessCodeRequestComponentFactory: HotAccessCodeRequestComponent.Factory,
private val hotAccessCodeRequesterProxy: HotWalletPasswordRequesterProxy,
private val userWalletsListRepository: UserWalletsListRepository,
private val cardRepository: CardRepository,
private val onboardingRepository: OnboardingRepository,
) : RoutingComponent,
AppComponentContext by context,
SnackbarHandler {
@ -87,6 +100,42 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
}
configureProxies()
initializeInitialNavigation()
}
private fun initializeInitialNavigation() {
if (initialStack.isNullOrEmpty()) {
componentScope.launch {
val initialRoute = resolveInitialRoute()
router.replaceAll(initialRoute)
}
}
}
private suspend fun resolveInitialRoute(): AppRoute {
val userWallets = userWalletsListRepository.userWalletsSync()
return when {
userWallets.isEmpty() -> {
val shouldShowTos = !cardRepository.isTangemTOSAccepted()
if (shouldShowTos) {
AppRoute.Disclaimer(isTosAccepted = false)
} else {
AppRoute.Home(launchMode = launchMode)
}
}
userWallets.any { it.isLocked } -> {
AppRoute.Welcome(
launchMode = launchMode,
)
}
else -> {
AppRoute.Wallet
}
}.also {
appRouterConfig.isInitialized.value = true
checkForUnfinishedBackup()
}
}
@Composable
@ -131,7 +180,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
messageSender.send(SnackbarMessage(message = TextReference.EMPTY))
}
// TODO: Find correct initial route here: [REDACTED_JIRA]
private fun getInitialStackOrInit(): List<AppRoute> = if (initialStack.isNullOrEmpty()) {
listOf(AppRoute.Initial)
} else {
@ -153,6 +201,17 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
@AssistedFactory
interface Factory : RoutingComponent.Factory {
override fun create(context: AppComponentContext, initialStack: List<AppRoute>?): DefaultRoutingComponent
override fun create(
context: AppComponentContext,
initialStack: List<AppRoute>?,
launchMode: InitScreenLaunchMode,
): DefaultRoutingComponent
}
private fun checkForUnfinishedBackup() {
componentScope.launch(dispatchers.main) {
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
}
}
}

View file

@ -4,12 +4,14 @@ import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
import com.tangem.tap.common.SnackbarHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
internal interface AppRouterConfig {
var routerScope: CoroutineScope?
var componentRouter: Router?
var stack: List<AppRoute>?
val isInitialized: MutableStateFlow<Boolean>
// TODO: Replace with UI message handler: [REDACTED_JIRA]
var snackbarHandler: SnackbarHandler?

View file

@ -4,11 +4,12 @@ import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
import com.tangem.tap.common.SnackbarHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
internal class MutableAppRouterConfig : AppRouterConfig {
override var routerScope: CoroutineScope? = null
override var componentRouter: Router? = null
override var stack: List<AppRoute>? = null
override var snackbarHandler: SnackbarHandler? = null
override val isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
}

View file

@ -155,7 +155,6 @@ internal class ChildFactory @Inject constructor(
context = context,
params = WelcomeComponent.Params(
launchMode = route.launchMode,
intent = route.intent,
),
componentFactory = welcomeComponentFactory,
)