Updated on 2026-08-14
This commit is contained in:
parent
64c7c64b3a
commit
713cad5fd7
26 changed files with 100 additions and 359 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -7,5 +7,5 @@ import android.content.Intent
|
|||
*/
|
||||
interface IntentHandler {
|
||||
|
||||
fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean
|
||||
fun handleIntent(intent: Intent?): Boolean
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -155,7 +155,6 @@ internal class ChildFactory @Inject constructor(
|
|||
context = context,
|
||||
params = WelcomeComponent.Params(
|
||||
launchMode = route.launchMode,
|
||||
intent = route.intent,
|
||||
),
|
||||
componentFactory = welcomeComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.os.Bundle
|
|||
import com.tangem.common.routing.bundle.RouteBundleParams
|
||||
import com.tangem.common.routing.bundle.bundle
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.feedback.models.WalletMetaInfo
|
||||
|
|
@ -32,9 +31,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class Welcome(
|
||||
@Deprecated("No longer used, will be removed in future releases")
|
||||
val launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
|
||||
// we still have this param to be handled by WalletConnectLinkIntentHandler in WelcomeMiddleware
|
||||
@Deprecated("No longer used, will be removed in future releases")
|
||||
val intent: SerializableIntent? = null,
|
||||
) : AppRoute(path = "/welcome"), RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.common.routing.entity
|
||||
|
||||
import android.os.Bundle
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SerializableBundle(
|
||||
val map: Map<String, String>,
|
||||
) {
|
||||
|
||||
constructor(bundle: Bundle) : this(
|
||||
map = bundle.keySet().mapNotNull { key ->
|
||||
bundle.getString(key)?.let { key to it }
|
||||
}.toMap(),
|
||||
)
|
||||
|
||||
fun toBundle(): Bundle {
|
||||
return Bundle().apply {
|
||||
map.forEach { (key, value) ->
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
package com.tangem.common.routing.entity
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SerializableIntent(
|
||||
val action: String?,
|
||||
val dataString: String?,
|
||||
val categories: Set<String>?,
|
||||
val type: String?,
|
||||
val packageValue: String?,
|
||||
val component: String?,
|
||||
val flags: Int,
|
||||
// CAUTION: works wrong with SerializableBundle constructor(bundle: Bundle), need to be removed
|
||||
val extras: SerializableBundle?,
|
||||
) {
|
||||
|
||||
constructor(intent: Intent) : this(
|
||||
action = intent.action,
|
||||
dataString = intent.dataString,
|
||||
categories = intent.categories,
|
||||
type = intent.type,
|
||||
packageValue = intent.`package`,
|
||||
component = intent.component?.flattenToString(),
|
||||
flags = intent.flags,
|
||||
extras = intent.extras?.let(::SerializableBundle),
|
||||
)
|
||||
|
||||
fun toIntent(): Intent {
|
||||
val intent = Intent()
|
||||
|
||||
intent.action = action
|
||||
intent.setDataAndType(
|
||||
dataString?.let { Uri.parse(it) },
|
||||
type,
|
||||
)
|
||||
categories?.let { categories ->
|
||||
for (category in categories) {
|
||||
intent.addCategory(category)
|
||||
}
|
||||
}
|
||||
intent.`package` = packageValue
|
||||
intent.component = component?.let { ComponentName.unflattenFromString(it) }
|
||||
intent.flags = flags
|
||||
extras?.let { intent.putExtras(it.toBundle()) }
|
||||
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,9 @@ internal class AccessCodeComponent @AssistedInject constructor(
|
|||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
if (!state.isConfirmMode) {
|
||||
DisableScreenshotsDisposableEffect()
|
||||
}
|
||||
|
||||
AccessCode(
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
if (shouldRequestPush) {
|
||||
// is yet blocked by [REDACTED_TASK_KEY]
|
||||
// stackNavigation.replaceAll(AddExistingWalletRoute.PushNotifications)
|
||||
stackNavigation.replaceAll(AddExistingWalletRoute.SetupFinished)
|
||||
stackNavigation.replaceAll(AddExistingWalletRoute.PushNotifications)
|
||||
} else {
|
||||
stackNavigation.replaceAll(AddExistingWalletRoute.SetupFinished)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.model.ManualBackupPhraseModel
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.ui.ManualBackupPhraseContent
|
||||
|
|
@ -22,6 +23,7 @@ internal class ManualBackupPhraseComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
DisableScreenshotsDisposableEffect()
|
||||
ManualBackupPhraseContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -73,9 +73,7 @@ internal class WalletActivationModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
val shouldRequestPush = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
if (shouldRequestPush) {
|
||||
// is yet blocked by [REDACTED_TASK_KEY]
|
||||
// stackNavigation.replaceAll(AddExistingWalletRoute.PushNotifications)
|
||||
stackNavigation.replaceAll(WalletActivationRoute.SetupFinished)
|
||||
stackNavigation.replaceAll(WalletActivationRoute.PushNotifications)
|
||||
} else {
|
||||
stackNavigation.replaceAll(WalletActivationRoute.SetupFinished)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue