Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-17 20:44:14 +04:00
commit 7a5933253b
154 changed files with 1767 additions and 1152 deletions

View file

@ -76,6 +76,7 @@ dependencies {
implementation(projects.domain.walletConnect)
implementation(projects.common)
implementation(projects.common.routing)
implementation(projects.core.analytics)
implementation(projects.core.analytics.models)
implementation(projects.core.navigation)

View file

@ -2,6 +2,7 @@ package com.tangem.tap
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.core.navigation.share.ShareManager
@ -28,7 +29,6 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -111,9 +111,9 @@ interface ApplicationEntryPoint {
fun getDetailsFeatureToggles(): DetailsFeatureToggles
fun getDetailsEntryPoint(): DetailsEntryPoint
fun getUrlOpener(): UrlOpener
fun getShareManager(): ShareManager
fun getAppRouter(): AppRouter
}

View file

@ -3,13 +3,12 @@ package com.tangem.tap
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.dispatchNavigationAction
import kotlinx.coroutines.*
import timber.log.Timber
import kotlin.time.Duration
@ -50,7 +49,7 @@ internal class LockUserWalletsTimer(
start()
if (shouldOpenWelcomeScreenOnResume) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
}
}
@ -128,7 +127,7 @@ internal class LockUserWalletsTimer(
if (wasApplicationStopped) {
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
} else {
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Welcome))
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
}
}
}

View file

@ -19,7 +19,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
@ -27,12 +26,16 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import by.kirich1409.viewbindingdelegate.viewBinding
import com.arkivanov.decompose.value.observe
import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.tangem.common.routing.AppRoute
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
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
@ -60,6 +63,8 @@ import com.tangem.tap.common.DialogManager
import com.tangem.tap.common.OnActivityResultCallback
import com.tangem.tap.common.SnackbarHandler
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
import com.tangem.tap.common.redux.NotificationsHandler
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -69,9 +74,10 @@ import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHan
import com.tangem.tap.features.main.MainViewModel
import com.tangem.tap.features.main.model.Toast
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphAction
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
@ -79,7 +85,6 @@ import com.tangem.wallet.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@ -162,6 +167,16 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var stakingRouter: StakingRouter
@Inject
@RootAppComponentContext
internal lateinit var rootComponentContext: AppComponentContext
@Inject
internal lateinit var appRouterConfig: AppRouterConfig
@Inject
internal lateinit var routingComponentFactory: RoutingComponent.Factory
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode?>
@ -188,6 +203,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
observeAppThemeModeUpdates()
setContentView(R.layout.activity_main)
installRouting()
initContent()
checkForNotificationPermission()
@ -199,6 +215,31 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
}
private fun installRouting() {
val routingComponent = routingComponentFactory.create(
context = rootComponentContext,
)
appRouterConfig.routerScope = lifecycleScope
appRouterConfig.componentRouter = routingComponent.router
routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack ->
appRouterConfig.stack = childStack.backStack
.plus(childStack.active)
.map { it.configuration }
when (val child = childStack.active.instance) {
is RoutingComponent.Child.Initial -> Unit
is RoutingComponent.Child.LegacyFragment -> {
supportFragmentManager.showFragmentAllowingStateLoss(child.name, child.fragmentProvider)
}
is RoutingComponent.Child.LegacyIntent -> {
startActivity(child.intent)
}
}
}
}
private fun observeStateUpdates() {
viewModel.state
.flowWithLifecycle(lifecycle)
@ -220,8 +261,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
private fun installActivityDependencies() {
store.dispatch(NavigationAction.ActivityCreated(WeakReference(this)))
cardSdkLifecycleObserver.onCreate(context = this)
tangemSdkManager = injectedTangemSdkManager
appStateHolder.tangemSdkManager = tangemSdkManager
@ -335,7 +374,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
override fun onDestroy() {
store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this)))
intentProcessor.removeAll()
cardSdkLifecycleObserver.onDestroy(this)
super.onDestroy()
@ -477,16 +515,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
store.dispatch(
NavigationAction.NavigateTo(
screen = AppScreen.Welcome,
bundle = intentWhichStartedActivity?.let {
bundleOf(WelcomeFragment.INITIAL_INTENT_KEY to it)
},
),
)
store.dispatchNavigationAction {
replaceAll(AppRoute.Welcome(intentWhichStartedActivity?.let(::SerializableIntent)))
}
} else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
lifecycleScope.launch {
intentProcessor.handleIntent(intentWhichStartedActivity, false)
}

View file

@ -13,6 +13,7 @@ import com.tangem.LogFormat
import com.tangem.TangemSdkLogger
import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.filter.OneTimeEventFilter
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
@ -43,7 +44,6 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.details.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
@ -185,14 +185,14 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val detailsFeatureToggles: DetailsFeatureToggles
get() = entryPoint.getDetailsFeatureToggles()
private val detailsEntryPoint: DetailsEntryPoint
get() = entryPoint.getDetailsEntryPoint()
private val urlOpener
get() = entryPoint.getUrlOpener()
private val shareManager
get() = entryPoint.getShareManager()
private val appRouter: AppRouter
get() = entryPoint.getAppRouter()
// endregion
override fun onCreate() {
@ -281,9 +281,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
blockchainSDKFactory = blockchainSDKFactory,
saveBlockchainErrorUseCase = saveBlockchainErrorUseCase,
detailsFeatureToggles = detailsFeatureToggles,
detailsEntryPoint = detailsEntryPoint,
urlOpener = urlOpener,
shareManager = shareManager,
appRouter = appRouter,
),
),
)

View file

@ -1,172 +1,51 @@
package com.tangem.tap.common.extensions
import android.os.Bundle
import androidx.fragment.app.*
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.FragmentShareTransition
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
import com.tangem.tap.features.details.ui.details.DetailsFragment
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.tangem.utils.Provider
import com.tangem.wallet.R
import timber.log.Timber
fun FragmentActivity.openFragment(
screen: AppScreen,
addToBackstack: Boolean,
bundle: Bundle? = null,
fgShareTransition: FragmentShareTransition? = null,
) {
val transaction = supportFragmentManager.beginTransaction().apply {
setReorderingAllowed(true)
}
fun FragmentManager.showFragmentAllowingStateLoss(name: String, fragmentProvider: Provider<Fragment>) {
Timber.d("Showing $name route")
val fragment = fragmentFactory(screen).apply {
arguments = bundle
val isPoppedBack = popBackStackImmediate(name, 0)
if (fgShareTransition != null) {
sharedElementEnterTransition = fgShareTransition.enterTransitionSet
sharedElementReturnTransition = fgShareTransition.exitTransitionSet
fgShareTransition.shareElements.forEach { shareElement ->
shareElement.wView.get()?.let { view ->
transaction.addSharedElement(view, shareElement.elementName)
}
}
if (!isPoppedBack) {
val fragment = fragmentProvider()
if (fragment is DialogFragment) {
fragment.showDialog(fragmentManager = this, name)
} else {
fragment.showFragment(fragmentManager = this, name)
}
}
if (screen.isDialogFragment && fragment is DialogFragment) {
fragment.showAllowingStateLoss(
fragmentManager = supportFragmentManager,
baseTransaction = transaction,
tag = screen.name,
addToBackstack = addToBackstack,
)
Timber.d("Route $name is shown")
} else {
transaction.replace(R.id.fragment_container, fragment, screen.name)
if (addToBackstack) {
transaction.addToBackStack(screen.name)
}
Timber.d("Route $name is found in backstack and shown")
}
}
private fun DialogFragment.showDialog(fragmentManager: FragmentManager, name: String) {
val transaction = fragmentManager.beginTransaction()
try {
transaction.addToBackStack(name)
show(transaction, name)
} catch (e: IllegalStateException) {
transaction.add(this, name)
transaction.addToBackStack(name)
transaction.commitAllowingStateLoss()
}
}
private fun DialogFragment.showAllowingStateLoss(
fragmentManager: FragmentManager,
baseTransaction: FragmentTransaction,
tag: String,
addToBackstack: Boolean,
) {
runCatching {
if (addToBackstack) baseTransaction.addToBackStack(tag)
show(baseTransaction, tag)
}
.onFailure { throwable ->
if (throwable is IllegalStateException) {
val transaction = fragmentManager.beginTransaction()
transaction.add(this, tag)
if (addToBackstack) transaction.addToBackStack(tag)
private fun Fragment.showFragment(fragmentManager: FragmentManager, name: String) {
val transaction = fragmentManager.beginTransaction()
transaction.commitAllowingStateLoss()
} else {
Timber.e(throwable)
}
}
}
transaction.replace(R.id.fragment_container, this, name)
transaction.addToBackStack(name)
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0
try {
this.supportFragmentManager.popBackStack(screen?.name, inclusiveFlag)
} catch (e: IllegalStateException) {
Timber.e(e)
}
}
fun FragmentActivity.getPreviousScreen(): AppScreen? {
val indexOfLastFragment = if (this.supportFragmentManager.backStackEntryCount > 0) {
this.supportFragmentManager.backStackEntryCount - 1
} else {
0
}
val tag = if (indexOfLastFragment < this.supportFragmentManager.backStackEntryCount) {
this.supportFragmentManager.getBackStackEntryAt(indexOfLastFragment).name
} else {
null
}
return tag?.let { AppScreen.valueOf(tag) }
}
@Suppress("ComplexMethod", "LongMethod")
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
AppScreen.OnboardingNote -> OnboardingNoteFragment()
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> OnboardingTwinsFragment()
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> {
store.inject(getDependency = DaggerGraphState::walletRouter).getEntryFragment()
}
AppScreen.Send -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::sendFeatureToggles)
if (featureToggles.isRedesignedSendEnabled) {
store.inject(getDependency = DaggerGraphState::sendRouter).getEntryFragment()
} else {
SendFragment()
}
}
AppScreen.Details -> {
val featureToggles = store.inject(getDependency = DaggerGraphState::detailsFeatureToggles)
if (featureToggles.isRedesignEnabled) {
store.inject(DaggerGraphState::detailsEntryPoint).entryFragment()
} else {
DetailsFragment()
}
}
AppScreen.DetailsSecurity -> SecurityModeFragment()
AppScreen.CardSettings -> CardSettingsFragment()
AppScreen.AppSettings -> AppSettingsFragment()
AppScreen.ResetToFactory -> ResetCardFragment()
AppScreen.AccessCodeRecovery -> AccessCodeRecoveryFragment()
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.ManageTokens -> TokensListFragment()
AppScreen.AddCustomToken -> AddCustomTokenFragment()
AppScreen.WalletDetails -> {
store.inject(getDependency = DaggerGraphState::tokenDetailsRouter).getEntryFragment()
}
AppScreen.WalletConnectSessions -> WalletConnectFragment()
AppScreen.QrScanning -> {
store.inject(getDependency = DaggerGraphState::qrScanningRouter).getEntryFragment()
}
AppScreen.ReferralProgram -> ReferralFragment()
AppScreen.Swap -> SwapFragment()
AppScreen.Welcome -> WelcomeFragment()
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
AppScreen.AppCurrencySelector -> AppCurrencySelectorFragment()
AppScreen.ModalNotification -> ModalNotificationBottomSheetFragment()
AppScreen.Staking -> store.inject(getDependency = DaggerGraphState::stakingRouter).getEntryFragment()
}
transaction.commitAllowingStateLoss()
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.common.extensions
import com.tangem.common.routing.AppRouter
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
@ -89,6 +90,10 @@ fun Store<AppState>.dispatchShare(url: String) {
inject(DaggerGraphState::shareManager).shareText(url)
}
fun Store<AppState>.dispatchNavigationAction(action: AppRouter.() -> Unit) {
inject(DaggerGraphState::appRouter).action()
}
inline fun <reified T> Store<AppState>.inject(getDependency: DaggerGraphState.() -> T?): T {
return requireNotNull(state.daggerGraphState.getDependency()) {
"${T::class.simpleName} isn't initialized "

View file

@ -1,7 +1,6 @@
package com.tangem.tap.common.redux
import com.tangem.tap.common.redux.global.globalReducer
import com.tangem.tap.common.redux.navigation.NavigationReducer
import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
@ -23,7 +22,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
if (action is AppAction.RestoreState) return action.state
return AppState(
navigationState = NavigationReducer.reduce(action, state),
globalState = globalReducer(action, state, appStateHolder),
homeState = HomeReducer.reduce(action, state),
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),

View file

@ -1,13 +1,11 @@
package com.tangem.tap.common.redux
import com.tangem.core.navigation.NavigationState
import com.tangem.domain.redux.DomainState
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.NetworkServices
import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.legacy.LegacyMiddleware
import com.tangem.tap.common.redux.navigation.navigationMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddleware
@ -40,7 +38,6 @@ import org.rekotlin.Middleware
import org.rekotlin.StateType
data class AppState(
val navigationState: NavigationState = NavigationState(),
val globalState: GlobalState = GlobalState(),
val homeState: HomeState = HomeState(),
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
@ -73,7 +70,6 @@ data class AppState(
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(
logMiddleware,
navigationMiddleware,
notificationsMiddleware,
GlobalMiddleware.handler,
HomeMiddleware.handler,

View file

@ -1,60 +0,0 @@
package com.tangem.tap.common.redux.navigation
import android.content.Intent
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.openFragment
import com.tangem.tap.common.extensions.popBackTo
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import org.rekotlin.Middleware
val navigationMiddleware: Middleware<AppState> = { _, state ->
{ next ->
{ action ->
if (action is NavigationAction) {
val navState = state()?.navigationState
when (action) {
is NavigationAction.NavigateTo -> {
navState?.activity?.get()?.openFragment(
screen = action.screen,
addToBackstack = action.addToBackstack,
fgShareTransition = action.fragmentShareTransition,
bundle = action.bundle,
)
}
is NavigationAction.PopBackTo -> {
if (navState?.backStack?.lastOrNull() != action.screen) {
when (val screen = action.screen) {
AppScreen.Home,
AppScreen.Welcome,
-> {
if (navState?.backStack?.contains(screen) == false) {
// Pop back to activity
navState.activity?.get()?.popBackTo(screen = null, inclusive = true)
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
} else {
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
}
}
else -> {
navState?.activity?.get()?.popBackTo(screen, action.inclusive)
}
}
}
}
is NavigationAction.OpenDocument -> {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = action.url
navState?.activity?.get()?.startActivity(intent)
}
is NavigationAction.ActivityCreated,
is NavigationAction.ActivityDestroyed,
-> Unit
}
}
next(action)
}
}
}

View file

@ -1,39 +0,0 @@
package com.tangem.tap.common.redux.navigation
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.NavigationState
import com.tangem.tap.common.extensions.getPreviousScreen
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
object NavigationReducer {
fun reduce(action: Action, state: AppState): NavigationState = internalReduce(action, state)
}
private fun internalReduce(action: Action, state: AppState): NavigationState {
val navigationAction = action as? NavigationAction ?: return state.navigationState
val navState = state.navigationState
return when (navigationAction) {
is NavigationAction.NavigateTo -> {
navState.copy(backStack = navState.backStack + navigationAction.screen)
}
is NavigationAction.PopBackTo -> {
if (navState.backStack.lastOrNull() == navigationAction.screen) return navState
val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
val index = navState.backStack.lastIndexOf(screen) + 1
state.navigationState.copy(backStack = navState.backStack.subList(0, index))
}
is NavigationAction.ActivityCreated -> navState.copy(activity = navigationAction.activity)
is NavigationAction.ActivityDestroyed -> {
when {
// Destroy the activity if it invoked for the same activity. Prevents overwriting to null if there is a
// new scan from the background [REDACTED_TASK_KEY]
navState.activity?.get() == navigationAction.activity.get() -> navState.copy(activity = null)
else -> navState
}
}
else -> navState
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.tap.di
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.tap.proxy.AppStateHolder
import dagger.Binds
@ -13,10 +12,6 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
internal interface AppStateHolderModule {
@Binds
@Singleton
fun bindsNavigationStateHolder(appStateHolder: AppStateHolder): ReduxNavController
@Binds
@Singleton
fun bindsReduxStateHolder(appStateHolder: AppStateHolder): ReduxStateHolder

View file

@ -0,0 +1,26 @@
package com.tangem.tap.di.routing
import com.tangem.common.routing.AppRouter
import com.tangem.tap.routing.ProxyAppRouter
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.tap.routing.configurator.MutableAppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AppRouterModule {
@Provides
@Singleton
fun provideAppRouter(config: AppRouterConfig, dispatchers: CoroutineDispatcherProvider): AppRouter =
ProxyAppRouter(config, dispatchers)
@Provides
@Singleton
fun provideAppRouterConfigurator(): AppRouterConfig = MutableAppRouterConfig()
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.routing
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.impl.DefaultRoutingComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.scopes.ActivityScoped
@Module
@InstallIn(ActivityComponent::class)
internal interface RoutingComponentModule {
@Binds
@ActivityScoped
fun bindRoutingComponentFactory(factory: DefaultRoutingComponent.Factory): RoutingComponent.Factory
}

View file

@ -5,12 +5,11 @@ import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.twinsIsTwinned
@ -21,6 +20,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
@ -118,7 +118,7 @@ internal object LegacyScanProcessor {
disclaimerWillShow()
store.dispatchWithMain(
DisclaimerAction.Show(
fromScreen = AppScreen.Home,
from = DisclaimerSource.Home,
callback = DisclaimerCallback(
onAccept = {
scope.launch(Dispatchers.Main) {
@ -170,7 +170,7 @@ internal object LegacyScanProcessor {
if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) {
onWalletNotCreated()
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) }
navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) }
} else {
delay(DELAY_SDK_DIALOG_CLOSE)
onSuccess(scanResponse)
@ -178,9 +178,9 @@ internal object LegacyScanProcessor {
}
}
private suspend inline fun navigateTo(screen: AppScreen, onProgressStateChange: (showProgress: Boolean) -> Unit) {
private suspend inline fun navigateTo(route: AppRoute, onProgressStateChange: (showProgress: Boolean) -> Unit) {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
store.dispatchNavigationAction { push(route) }
onProgressStateChange(false)
}
}

View file

@ -3,16 +3,15 @@ package com.tangem.tap.domain.scanCard
import arrow.fx.coroutines.resourceScope
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.StateDialog
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.domain.scanCard.chains.*
import com.tangem.tap.domain.scanCard.utils.ScanCardExceptionConverter
@ -132,8 +131,8 @@ internal object UseCaseScanProcessor {
action()
}
private suspend inline fun navigateTo(screen: AppScreen) {
private suspend inline fun navigateTo(route: AppRoute) {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
store.dispatchNavigationAction { push(route) }
}
}

View file

@ -2,8 +2,8 @@ package com.tangem.tap.domain.scanCard.chains
import arrow.core.left
import arrow.core.right
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.common.util.twinsIsTwinned
@ -55,8 +55,8 @@ class CheckForOnboardingChain(
canSkipBackup = previousChainResult.card.canSkipBackup,
),
)
val appScreen = OnboardingHelper.whereToNavigate(previousChainResult)
ScanChainException.OnboardingNeeded(appScreen).left()
val route = OnboardingHelper.whereToNavigate(previousChainResult)
ScanChainException.OnboardingNeeded(route).left()
}
else -> {
Analytics.setContext(previousChainResult)
@ -69,7 +69,7 @@ class CheckForOnboardingChain(
store.dispatchOnMain(
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
)
ScanChainException.OnboardingNeeded(AppScreen.OnboardingTwins).left()
ScanChainException.OnboardingNeeded(AppRoute.OnboardingTwins).left()
} else {
delay(DELAY_SDK_DIALOG_CLOSE)
previousChainResult.right()

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.scanCard.chains
import arrow.core.left
import arrow.core.right
import com.tangem.core.navigation.AppScreen
import com.tangem.domain.card.ScanCardException
import com.tangem.domain.core.chain.Chain
import com.tangem.domain.core.chain.ResultChain
@ -13,6 +12,7 @@ import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.createDisclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerCallback
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import kotlinx.coroutines.suspendCancellableCoroutine
import org.rekotlin.Store
import kotlin.coroutines.resume
@ -50,7 +50,7 @@ internal class DisclaimerChain(
return suspendCancellableCoroutine { continuation ->
store.dispatchOnMain(
DisclaimerAction.Show(
fromScreen = AppScreen.Home,
from = DisclaimerSource.Home,
callback = DisclaimerCallback(
onAccept = {
if (continuation.isActive) {

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain.scanCard.chains
import com.tangem.core.navigation.AppScreen
import com.tangem.common.routing.AppRoute
import com.tangem.domain.card.ScanCardException
sealed class ScanChainException : ScanCardException.ChainException() {
@ -19,5 +19,5 @@ sealed class ScanChainException : ScanCardException.ChainException() {
*
* @param onboardingRoute route where to navigate
* */
data class OnboardingNeeded(val onboardingRoute: AppScreen) : ScanChainException()
data class OnboardingNeeded(val onboardingRoute: AppRoute) : ScanChainException()
}

View file

@ -14,7 +14,8 @@ import androidx.lifecycle.Lifecycle
import androidx.transition.TransitionInflater
import com.google.android.material.snackbar.Snackbar
import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import com.tangem.wallet.R
@ -36,7 +37,7 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP
}
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View file

@ -1,9 +1,11 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.popTo
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store
import com.tangem.wallet.R
@ -12,11 +14,11 @@ import com.tangem.wallet.R
internal class DefaultCustomTokenRouter : CustomTokenRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
}
override fun openWalletScreen() {
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
}
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
@ -32,7 +34,7 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
val alert = AppDialog.SimpleOkDialogRes(
headerId = R.string.common_error,
messageId = R.string.common_unknown_error,
onOk = { store.dispatch(NavigationAction.PopBackTo()) },
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
)
store.dispatchDialogShow(alert)
}

View file

@ -5,9 +5,11 @@ import com.tangem.common.*
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.core.UserCodeRequestPolicy
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -70,7 +72,8 @@ class DetailsMiddleware {
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
is DetailsAction.ReCreateTwinsWallet -> {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
}
is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action)
is DetailsAction.ScanCard -> scanCard(state)
@ -90,7 +93,7 @@ class DetailsMiddleware {
store.dispatch(DetailsAction.ReCreateTwinsWallet)
return
} else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.ResetToFactory))
store.dispatchNavigationAction { push(AppRoute.ResetToFactory) }
}
}
is DetailsAction.ResetToFactory.Proceed -> {
@ -131,15 +134,15 @@ class DetailsMiddleware {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
if (selectedUserWallet != null) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
store.onUserWalletSelected(selectedUserWallet)
} else {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
if (isLocked && userWalletsListManager.hasUserWallets) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
} else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
}
}
}
@ -163,7 +166,7 @@ class DetailsMiddleware {
fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) {
when (action) {
is DetailsAction.ManageSecurity.OpenSecurity -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
store.dispatchNavigationAction { push(AppRoute.DetailsSecurity) }
}
is DetailsAction.ManageSecurity.SaveChanges -> {
val cardSettingsState = detailsState.cardSettingsState
@ -182,7 +185,7 @@ class DetailsMiddleware {
is CompletionResult.Success -> {
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption))
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
}
is CompletionResult.Failure -> {
@ -392,7 +395,7 @@ class DetailsMiddleware {
deleteSavedAccessCodes()
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
return CompletionResult.Success(Unit)
}
@ -431,7 +434,7 @@ class DetailsMiddleware {
when (action) {
is DetailsAction.AccessCodeRecovery.Open -> {
Analytics.send(Settings.CardSettings.AccessCodeRecoveryButton())
store.dispatch(NavigationAction.NavigateTo(AppScreen.AccessCodeRecovery))
store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
}
is DetailsAction.AccessCodeRecovery.SaveChanges -> {
scope.launch {
@ -443,7 +446,7 @@ class DetailsMiddleware {
AnalyticsParam.AccessCodeRecoveryStatus.from(action.enabled),
),
)
store.dispatchOnMain(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
store.dispatchOnMain(
DetailsAction.AccessCodeRecovery.SaveChanges.Success(action.enabled),
)
@ -502,7 +505,7 @@ class DetailsMiddleware {
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
},
disclaimerWillShow = {
store.dispatchOnMain(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
onSuccess = { scanResponse ->
createUserWallet(scanResponse)
@ -546,7 +549,7 @@ class DetailsMiddleware {
store.onUserWalletSelected(userWallet)
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
}
.doOnFailure { error ->
if (error is UserWalletsListError.WalletAlreadySaved) {
@ -554,7 +557,7 @@ class DetailsMiddleware {
store.onUserWalletSelected(userWallet)
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} else {
Timber.e(error, "Unable to create user wallet")
handleError(error, prevUseBiometricsForAccessCode)

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.details.redux
import com.tangem.core.navigation.AppScreen
import com.tangem.common.routing.AppRoute
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
@ -69,10 +69,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
}
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState {
val router = store.inject(DaggerGraphState::appRouter)
return DetailsState(
scanResponse = action.scanResponse,
// If the current screen is ResetToFactory, we must save the ResetToFactory's state
cardSettingsState = if (store.state.navigationState.backStack.lastOrNull() == AppScreen.ResetToFactory) {
cardSettingsState = if (router.stack.lastOrNull() is AppRoute.ResetToFactory) {
state.detailsState.cardSettingsState
} else {
null

View file

@ -1,19 +1,17 @@
package com.tangem.tap.features.details.redux.walletconnect
import androidx.core.os.bundleOf
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
import com.tangem.tap.domain.walletconnect2.domain.models.Account
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
@ -62,14 +60,7 @@ class WalletConnectMiddleware {
// TODO check
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
} else {
store.dispatchOnMain(
NavigationAction.NavigateTo(
screen = AppScreen.QrScanning,
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
),
),
)
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
}
}
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {

View file

@ -2,8 +2,9 @@ package com.tangem.tap.features.details.ui.appcurrency
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase
@ -20,7 +21,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase,
private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase,
private val reduxNavController: ReduxNavController,
private val router: AppRouter,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), AppCurrencySelectorIntents {
@ -34,7 +35,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
val uiState: StateFlow<AppCurrencySelectorState> = stateController.stateFlow
override fun onBackClick() {
reduxNavController.popBackStack()
router.pop()
}
override fun onSearchClick() {
@ -52,7 +53,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
analyticsEventHandler.send(
event = Settings.AppSettings.MainCurrencyChanged(currencyType = currency.name),
)
reduxNavController.popBackStack()
router.pop()
}
}
}

View file

@ -6,10 +6,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -36,7 +38,7 @@ internal class AppSettingsFragment : ComposeFragment() {
state = state,
onBackClick = {
store.dispatch(DetailsAction.ResetCardSettingsData)
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
)
}

View file

@ -4,13 +4,14 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.features.details.redux.AppSetting
@ -128,7 +129,7 @@ internal class AppSettingsViewModel @Inject constructor(
}
private fun showAppCurrencySelector() {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppCurrencySelector))
store.dispatchNavigationAction { push(AppRoute.AppCurrencySelector) }
}
private fun showThemeModeSelector(selectedMode: AppThemeMode) {

View file

@ -4,9 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.fragment.app.viewModels
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -29,7 +31,7 @@ internal class CardSettingsFragment : ComposeFragment() {
state = viewModel.screenState.value,
onBackClick = {
store.dispatch(DetailsAction.ResetCardSettingsData)
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
)
}

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -28,7 +29,7 @@ class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber<DetailsSta
override fun ScreenContent(modifier: Modifier) {
AccessCodeRecoveryScreen(
state = screenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}

View file

@ -3,13 +3,14 @@ package com.tangem.tap.features.details.ui.details
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -45,7 +46,7 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
DetailsScreen(
modifier = modifier,
state = detailsViewModel.detailsScreenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}

View file

@ -3,11 +3,11 @@ package com.tangem.tap.features.details.ui.details
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
@ -16,16 +16,14 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.feedback.FeedbackEmail
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.home.LocaleRegionProvider
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
import com.tangem.tap.scope
@ -129,15 +127,17 @@ internal class DetailsViewModel(
}
private fun navigateToTesterMenu() {
store.state.daggerGraphState.testerRouter?.startTesterScreen()
store.dispatchNavigationAction {
push(AppRoute.TesterMenu)
}
}
private fun navigateToToS() {
store.dispatchOnMain(DisclaimerAction.Show(AppScreen.Details))
store.dispatchOnMain(DisclaimerAction.Show(DisclaimerSource.Details))
}
private fun navigateToReferralProgram() {
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
store.dispatchNavigationAction { push(AppRoute.ReferralProgram) }
}
private fun sendFeedback() {
@ -147,12 +147,12 @@ internal class DetailsViewModel(
private fun navigateToAppSettings() {
Analytics.send(Settings.ButtonAppSettings())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppSettings))
store.dispatchNavigationAction { push(AppRoute.AppSettings) }
}
private fun navigateToCardSettings() {
Analytics.send(Settings.ButtonCardSettings())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.CardSettings))
store.dispatchNavigationAction { push(AppRoute.CardSettings) }
}
private fun linkMoreCards() {
@ -165,7 +165,7 @@ internal class DetailsViewModel(
val scanResponse = selectedUserWallet.scanResponse
Analytics.addContext(scanResponse)
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
}
private fun scanAndSaveUserWallet() {
@ -175,7 +175,7 @@ internal class DetailsViewModel(
private fun navigateToWalletConnect() {
Analytics.send(Settings.ButtonWalletConnect())
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
store.dispatchNavigationAction { push(AppRoute.WalletConnectSessions) }
}
private fun handleSocialNetworkClick(link: SocialNetworkLink) {

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -30,7 +31,7 @@ internal class ResetCardFragment : ComposeFragment(), StoreSubscriber<DetailsSta
ResetCardScreen(
state = state,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
modifier = modifier,
)
}

View file

@ -2,13 +2,15 @@ package com.tangem.tap.features.details.ui.resetcard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.redux.CardSettingsState
import com.tangem.tap.features.details.redux.DetailsAction.ResetToFactory
@ -165,9 +167,9 @@ internal class ResetCardViewModel @Inject constructor(
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull()
if (newSelectedWallet != null) {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
} else {
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
}
}

View file

@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -29,7 +30,7 @@ internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber<Details
SecurityModeScreen(
modifier = modifier,
state = screenState.value,
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}

View file

@ -6,11 +6,12 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
@ -41,7 +42,7 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
modifier = modifier,
state = state,
onBackClick = {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
)
}

View file

@ -2,12 +2,10 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.common.routing.AppRoute
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.store
@ -22,14 +20,9 @@ object ClipboardOrScanQrDialog {
store.dispatch(WalletConnectAction.OpenSession(wcUri))
}
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
store.dispatch(
NavigationAction.NavigateTo(
screen = AppScreen.QrScanning,
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
),
),
)
store.dispatchNavigationAction {
push(AppRoute.QrScanning(source = SourceType.WALLET_CONNECT))
}
}
setOnDismissListener {
store.dispatch(GlobalAction.HideDialog)

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.disclaimer.redux
import com.tangem.core.navigation.AppScreen
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.disclaimer.Disclaimer
import org.rekotlin.Action
@ -10,7 +9,7 @@ sealed class DisclaimerAction : Action {
data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction()
data class Show(
val fromScreen: AppScreen,
val from: DisclaimerSource,
val callback: DisclaimerCallback? = null,
) : DisclaimerAction()

View file

@ -1,7 +1,8 @@
package com.tangem.tap.features.disclaimer.redux
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.mainScope
import com.tangem.tap.store
@ -25,17 +26,19 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
when (action) {
is DisclaimerAction.Show -> {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
store.dispatchNavigationAction {
push(AppRoute.Disclaimer(isTosAccepted = action.from == DisclaimerSource.Details))
}
}
is DisclaimerAction.AcceptDisclaimer -> {
mainScope.launch {
state.disclaimer.accept()
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
state.callback?.onAccept?.invoke()
}
}
is DisclaimerAction.OnBackPressed -> {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
state.callback?.onDismiss?.invoke()
}
}

View file

@ -17,7 +17,7 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
disclaimer = action.disclaimer,
)
is DisclaimerAction.Show -> disclaimerState.copy(
showedFromScreen = action.fromScreen,
showedFrom = action.from,
callback = action.callback,
)
is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy(

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.disclaimer.redux
enum class DisclaimerSource {
Home, Details
}

View file

@ -1,7 +1,7 @@
package com.tangem.tap.features.disclaimer.redux
import com.tangem.common.extensions.VoidCallback
import com.tangem.core.navigation.AppScreen
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.DummyDisclaimer
@ -9,7 +9,7 @@ import org.rekotlin.StateType
data class DisclaimerState(
val disclaimer: Disclaimer = DummyDisclaimer(),
val showedFromScreen: AppScreen = AppScreen.Home,
val showedFrom: DisclaimerSource = DisclaimerSource.Home,
val callback: DisclaimerCallback? = null,
val progressState: ProgressState? = null,
) : StateType

View file

@ -7,7 +7,7 @@ import android.webkit.WebView
import androidx.lifecycle.lifecycleScope
import androidx.transition.TransitionInflater
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.core.navigation.AppScreen
import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.tap.common.entities.ProgressState
import com.tangem.tap.common.extensions.beginDelayedTransition
@ -18,6 +18,7 @@ import com.tangem.tap.features.BaseFragment
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.disclaimer.Disclaimer
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
import com.tangem.tap.store
import com.tangem.wallet.R
@ -76,17 +77,14 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs
override fun configureTransitions() {
val inflater = TransitionInflater.from(requireContext())
when (store.state.disclaimerState.showedFromScreen) {
AppScreen.Home -> {
when (store.state.disclaimerState.showedFrom) {
DisclaimerSource.Home -> {
enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
}
AppScreen.Details -> {
DisclaimerSource.Details -> {
super.configureTransitions()
}
else -> {
/* no-op */
}
}
}

View file

@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.tokens.TokensAction
import com.tangem.tap.common.analytics.events.IntroductionProcess
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
@ -86,7 +87,7 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
},
onSearchTokensClick = {
Analytics.send(IntroductionProcess.ButtonTokensList())
store.dispatch(NavigationAction.NavigateTo(AppScreen.ManageTokens))
store.dispatchNavigationAction { push(AppRoute.ManageTokens) }
store.dispatch(TokensAction.SetArgs.ReadAccess)
},
)

View file

@ -4,11 +4,10 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
@ -119,7 +118,7 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
scope.launch { store.onUserWalletSelected(userWallet = userWallet) }
}
.doOnResult {
navigateTo(AppScreen.Wallet)
navigateTo(AppRoute.Wallet)
}
}
@ -143,8 +142,8 @@ private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
}
}
private suspend fun navigateTo(appScreen: AppScreen) {
store.dispatchOnMain(NavigationAction.NavigateTo(appScreen))
private suspend fun navigateTo(route: AppRoute) {
store.dispatchNavigationAction { push(route) }
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
}

View file

@ -3,11 +3,11 @@ package com.tangem.tap.features.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
@ -36,7 +36,7 @@ import javax.inject.Inject
internal class MainViewModel @Inject constructor(
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
private val listenToFlipsUseCase: ListenToFlipsUseCase,
private val reduxNavController: ReduxNavController,
private val router: AppRouter,
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
@ -152,7 +152,7 @@ internal class MainViewModel @Inject constructor(
if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) {
listenToFlipsUseCase.changeUpdateEnabled(false)
stateHolder.updateWithHiddenBalancesNotification()
reduxNavController.navigate(NavigationAction.NavigateTo(AppScreen.ModalNotification))
router.push(AppRoute.ModalNotification)
}
}
.launchIn(viewModelScope)

View file

@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.models.scan.CardDTO
@ -56,18 +56,18 @@ object OnboardingHelper {
}
}
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
return when (val type = scanResponse.productType) {
ProductType.Note -> AppScreen.OnboardingNote
ProductType.Note -> AppRoute.OnboardingNote
ProductType.Wallet,
ProductType.Wallet2,
ProductType.Ring,
-> if (scanResponse.card.settings.isBackupAllowed) {
AppScreen.OnboardingWallet
AppRoute.OnboardingWallet()
} else {
AppScreen.OnboardingOther
AppRoute.OnboardingOther
}
ProductType.Twins -> AppScreen.OnboardingTwins
ProductType.Twins -> AppRoute.OnboardingTwins
ProductType.Start2Coin,
ProductType.Visa,
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
@ -111,14 +111,14 @@ object OnboardingHelper {
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchNavigationAction { push(AppRoute.Wallet) }
delay(timeMillis = 1_800)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
}
// If device has no biometry and save wallet screen has been shown, then go through old scenario
else -> {
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchNavigationAction { push(AppRoute.Wallet) }
}
}
}

View file

@ -2,8 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext
@ -204,7 +205,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
OnboardingDialog.InterruptOnboarding(
onOk = {
OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
),
)

View file

@ -3,9 +3,10 @@ package com.tangem.tap.features.onboarding.products.twins.redux
import com.tangem.blockchain.extensions.Result
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.common.extensions.makePrimaryWalletManager
import com.tangem.domain.common.extensions.withMainContext
@ -39,6 +40,7 @@ import kotlinx.coroutines.runBlocking
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
import org.rekotlin.Middleware
import kotlin.reflect.KClass
object TwinCardsMiddleware {
val handler = twinsWalletMiddleware
@ -328,7 +330,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
if (walletsRepository.shouldSaveUserWalletsSync()) {
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
} else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
}
}
}
@ -352,7 +354,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(TwinCardsAction.CardsManager.Release)
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
store.dispatchOnMain(NavigationAction.PopBackTo(getPopBackScreen()))
store.dispatchNavigationAction { popTo(routeClass = getPopBackScreen()) }
}
}
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
@ -362,18 +364,19 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
}
private fun getPopBackScreen(): AppScreen {
private fun getPopBackScreen(): KClass<out AppRoute> {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
return if (userWalletsListManager.hasUserWallets) {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
if (isLocked) {
AppScreen.Welcome
AppRoute.Welcome::class
} else {
AppScreen.Wallet
AppRoute.Wallet::class
}
} else {
AppScreen.Home
AppRoute.Home::class
}
}

View file

@ -7,16 +7,16 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.ifNotNull
import com.tangem.common.extensions.toHexString
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.Artwork
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.models.Artwork
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
@ -25,10 +25,7 @@ import com.tangem.operations.backup.BackupService
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
@ -164,7 +161,7 @@ private fun handleWalletAction(action: Action) {
store.dispatch(GlobalAction.Onboarding.Stop)
if (scanResponse == null) {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(HomeAction.ReadCard(scope = action.scope))
} else {
val backupState = store.state.onboardingWalletState.backupState
@ -529,7 +526,8 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
addedBackupCardsCount = backupService.addedBackupCardsCount,
),
)
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
}
is BackupAction.SkipBackup -> {
Analytics.send(Onboarding.Backup.Skipped())
@ -614,7 +612,7 @@ private fun handleOnBackPressed(state: OnboardingWalletState) {
}
BackupStep.Finished -> {
OnboardingHelper.onInterrupted()
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
}
}
}
@ -625,7 +623,7 @@ private fun showInterruptOnboardingDialog() {
onOk = {
OnboardingHelper.onInterrupted()
store.dispatch(BackupAction.DiscardBackup)
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
},
),
)

View file

@ -21,13 +21,13 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.tangem.common.CardIdFormatter
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardIdDisplayFormat
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.ui.extensions.setStatusBarColor
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
import com.tangem.feature.onboarding.navigation.OnboardingRouter
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
@ -66,7 +66,7 @@ class OnboardingWalletFragment :
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
private val canSkipBackup by lazy { arguments?.getBoolean(OnboardingRouter.CAN_SKIP_BACKUP) ?: true }
private val canSkipBackup by lazy { arguments?.getBoolean(AppRoute.OnboardingWallet.CAN_SKIP_BACKUP_KEY) ?: true }
private lateinit var seedPhraseStateHandler: OnboardingSeedPhraseStateHandler

View file

@ -3,26 +3,22 @@ package com.tangem.tap.features.saveWallet.redux
import com.tangem.common.*
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import org.rekotlin.Store
import timber.log.Timber
internal class SaveWalletMiddleware {
@ -80,7 +76,7 @@ internal class SaveWalletMiddleware {
Timber.e(error, "Unable to save user wallet")
}
.doOnSuccess { mainScope.launch { store.onUserWalletSelected(userWallet) } }
.doOnResult { store.navigateToWallet() }
.doOnResult { navigateToWallet() }
}
}
@ -130,7 +126,7 @@ internal class SaveWalletMiddleware {
)
store.dispatchWithMain(SaveWalletAction.AllowToUseBiometrics.Success)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
}
private fun dismiss(state: SaveWalletState) {
@ -159,13 +155,9 @@ internal class SaveWalletMiddleware {
}
}
private suspend fun Store<AppState>.navigateToWallet() {
dispatchWithMain(
if (store.state.navigationState.backStack.contains(AppScreen.Wallet)) {
NavigationAction.PopBackTo(AppScreen.Wallet)
} else {
NavigationAction.NavigateTo(AppScreen.Wallet)
},
)
private fun navigateToWallet() {
store.dispatchNavigationAction {
replaceAll(AppRoute.Wallet)
}
}
}

View file

@ -14,10 +14,10 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchainsdk.utils.minimalAmount
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.domain.demo.DemoTransactionSender
@ -270,7 +270,7 @@ private fun sendTransaction(
),
)
Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
}
}
is SimpleResult.Failure -> {
@ -326,7 +326,7 @@ private fun sendTransaction(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_done,
messageId = R.string.alert_demo_feature_disabled,
onOk = { dispatch(NavigationAction.PopBackTo()) },
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
),
)
}

View file

@ -8,7 +8,6 @@ import android.text.method.DigitsKeyListener
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.core.os.bundleOf
import androidx.core.view.postDelayed
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.viewModels
@ -20,19 +19,18 @@ import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.textfield.TextInputEditText
import com.tangem.Message
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.sdk.extensions.hideSoftKeyboard
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.getFromClipboard
import com.tangem.tap.common.extensions.setOnImeActionListener
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
@ -162,14 +160,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
imvQrCode.setOnClickListener {
Analytics.send(Token.Send.ButtonQRCode())
store.dispatchOnMain(
NavigationAction.NavigateTo(
screen = AppScreen.QrScanning,
bundle = bundleOf(
QrScanningRouter.SOURCE_KEY to SourceType.SEND,
),
),
)
store.dispatchNavigationAction {
push(AppRoute.QrScanning(source = SourceType.SEND))
}
}
}
@ -370,7 +363,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
override fun handleOnBackPressed() {
val externalTransactionData = store.state.sendState.externalTransactionData
if (externalTransactionData == null) {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
} else {
store.dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.ui
import androidx.lifecycle.*
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
@ -12,7 +13,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.di.DelayedWork
import com.tangem.tap.features.send.redux.AddressActionUi
@ -52,7 +52,7 @@ internal class SendViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private val cryptoCurrency: CryptoCurrency? = savedStateHandle[SendRouter.CRYPTO_CURRENCY_KEY]
private val cryptoCurrency: CryptoCurrency? = savedStateHandle[AppRoute.Send.CRYPTO_CURRENCY_KEY]
override fun onCreate(owner: LifecycleOwner) {
getBalanceHidingSettingsUseCase()

View file

@ -1,9 +1,11 @@
package com.tangem.tap.features.tokens.impl.presentation.router
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store
@ -18,11 +20,11 @@ import com.tangem.wallet.R
internal class DefaultTokensListRouter : TokensListRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
}
override fun openAddCustomTokenScreen() {
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
store.dispatchNavigationAction { push(AppRoute.AddCustomToken) }
}
override fun showAddressCopiedNotification() {
@ -54,7 +56,7 @@ internal class DefaultTokensListRouter : TokensListRouter {
val alert = AppDialog.SimpleOkDialogRes(
headerId = R.string.common_error,
messageId = R.string.common_unknown_error,
onOk = { store.dispatch(NavigationAction.PopBackTo()) },
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
)
store.dispatchDialogShow(alert)
}

View file

@ -3,8 +3,8 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
import arrow.core.Either
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.common.util.derivationStyleProvider
@ -16,7 +16,7 @@ import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
@ -126,7 +126,7 @@ internal class TokensListMigration(
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
store.dispatchDebugErrorNotification(message = "Nothing to save")
store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
return
}

View file

@ -11,9 +11,9 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.getGreyedOutIconRes
import com.tangem.domain.card.DerivePublicKeysUseCase
@ -26,7 +26,7 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.common.extensions.getNetworkName
import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType
@ -326,7 +326,7 @@ internal class TokensListViewModel @Inject constructor(
)
uiState = state.copy(isSavingInProgress = false)
store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
}
}

View file

@ -1,18 +1,16 @@
package com.tangem.tap.features.wallet.redux.middlewares
import androidx.core.os.bundleOf
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
import com.tangem.tap.common.extensions.*
@ -150,7 +148,7 @@ object TradeCryptoMiddleware {
}
private fun openReceiptUrl(transactionId: String) {
store.dispatchOnMain(NavigationAction.PopBackTo())
store.dispatchNavigationAction(AppRouter::pop)
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
action = CurrencyExchangeManager.Action.Sell,
transactionId = transactionId,
@ -158,11 +156,7 @@ object TradeCryptoMiddleware {
}
private fun openSwap(currency: CryptoCurrency) {
val bundle = bundleOf(
SwapFragment.CURRENCY_BUNDLE_KEY to currency,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
store.dispatchNavigationAction { push(AppRoute.Swap(currency = currency)) }
}
private fun handleSendToken(action: TradeCryptoAction.SendToken) {
@ -214,11 +208,12 @@ object TradeCryptoMiddleware {
)
}
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
val route = AppRoute.Send(
currency = currency,
userWalletId = action.userWallet.walletId,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
store.dispatchNavigationAction { push(route) }
}
}
@ -285,17 +280,17 @@ object TradeCryptoMiddleware {
)
}
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
val route = AppRoute.Send(
currency = currency,
userWalletId = action.userWallet.walletId,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
store.dispatchNavigationAction { push(route) }
}
}
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
handleNewSend(
userWalletId = action.userWallet.walletId.stringValue,
userWalletId = action.userWallet.walletId,
txInfo = action.transactionInfo,
currency = action.tokenCurrency,
)
@ -303,25 +298,26 @@ object TradeCryptoMiddleware {
private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) {
handleNewSend(
userWalletId = action.userWallet.walletId.stringValue,
userWalletId = action.userWallet.walletId,
txInfo = action.transactionInfo,
currency = action.coinStatus.currency,
)
}
private fun handleNewSend(
userWalletId: String,
userWalletId: UserWalletId,
txInfo: TradeCryptoAction.TransactionInfo?,
currency: CryptoCurrency,
) {
val bundle = bundleOf(
SendRouter.CRYPTO_CURRENCY_KEY to currency,
SendRouter.USER_WALLET_ID_KEY to userWalletId,
SendRouter.TRANSACTION_ID_KEY to txInfo?.transactionId,
SendRouter.DESTINATION_ADDRESS_KEY to txInfo?.destinationAddress,
SendRouter.AMOUNT_KEY to txInfo?.amount,
SendRouter.TAG_KEY to txInfo?.tag,
val route = AppRoute.Send(
currency = currency,
userWalletId = userWalletId,
transactionId = txInfo?.transactionId,
destinationAddress = txInfo?.destinationAddress,
amount = txInfo?.amount,
tag = txInfo?.tag,
)
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
store.dispatchNavigationAction { push(route) }
}
}

View file

@ -6,11 +6,12 @@ import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
@ -98,7 +99,7 @@ internal class WelcomeMiddleware {
signInType = Basic.SignedIn.SignInType.Biometric,
)
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchNavigationAction { push(AppRoute.Wallet) }
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Success)
store.onUserWalletSelected(userWallet = selectedUserWallet)
@ -130,7 +131,7 @@ internal class WelcomeMiddleware {
.doOnSuccess {
sendSignedInAnalyticsEvent(scanResponse = scanResponse, signInType = Basic.SignedIn.SignInType.Card)
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
store.dispatchNavigationAction { push(AppRoute.Wallet) }
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
store.onUserWalletSelected(userWallet = userWallet)
@ -170,7 +171,7 @@ internal class WelcomeMiddleware {
}
.doOnResult {
store.dispatchWithMain(WelcomeAction.CloseError)
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
}
}

View file

@ -88,8 +88,4 @@ internal class WelcomeFragment : ComposeFragment() {
}
}
}
internal companion object {
const val INITIAL_INTENT_KEY = "intent"
}
}

View file

@ -1,8 +1,11 @@
package com.tangem.tap.features.welcome.ui
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.*
import com.tangem.common.core.TangemError
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.analytics.Analytics
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.tap.common.analytics.events.SignIn
@ -26,7 +29,8 @@ internal class WelcomeViewModel @Inject constructor(
StoreSubscriber<WelcomeState>,
DefaultLifecycleObserver {
private val initialIntent: Intent? = savedStateHandle[WelcomeFragment.INITIAL_INTENT_KEY]
private val initialIntent: SerializableIntent? = savedStateHandle.get<Bundle>(AppRoute.Welcome.INITIAL_INTENT_KEY)
?.let { it.unbundle(SerializableIntent.serializer()) }
private val stateInternal = MutableStateFlow(WelcomeScreenState())
val state: StateFlow<WelcomeScreenState> = stateInternal
@ -38,7 +42,7 @@ internal class WelcomeViewModel @Inject constructor(
initGlobalState()
val welcomeAction = if (initialIntent != null) {
WelcomeAction.ProceedWithIntent(initialIntent)
WelcomeAction.ProceedWithIntent(initialIntent.toIntent())
} else {
WelcomeAction.ProceedWithBiometrics()
}

View file

@ -1,15 +1,11 @@
package com.tangem.tap.proxy
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.navigation.ReduxNavController
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
@ -23,7 +19,7 @@ import javax.inject.Inject
* Holds objects from old modules, that missing in DI graph.
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI.
*/
class AppStateHolder @Inject constructor() : ReduxNavController, ReduxStateHolder {
class AppStateHolder @Inject constructor() : ReduxStateHolder {
@Deprecated("Use scan response from selected user wallet")
var scanResponse: ScanResponse? = null
@ -35,16 +31,6 @@ class AppStateHolder @Inject constructor() : ReduxNavController, ReduxStateHolde
return scanResponse?.card
}
override fun navigate(action: NavigationAction) {
mainStore?.dispatchOnMain(action)
}
override fun getBackStack(): List<AppScreen> = mainStore?.state?.navigationState?.backStack.orEmpty()
override fun popBackStack(screen: AppScreen?) {
mainStore?.dispatchOnMain(NavigationAction.PopBackTo(screen))
}
override fun dispatch(action: Action) {
mainStore?.dispatch(action)
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux
import com.tangem.TangemSdkLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.email.EmailSender
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
@ -26,7 +27,6 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
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.DetailsEntryPoint
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.managetokens.featuretoggles.ManageTokensFeatureToggles
import com.tangem.features.managetokens.navigation.ManageTokensUi
@ -81,8 +81,8 @@ data class DaggerGraphState(
val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase? = null,
val assetLoader: AssetLoader? = null,
val detailsFeatureToggles: DetailsFeatureToggles? = null,
val detailsEntryPoint: DetailsEntryPoint? = null,
val stakingRouter: StakingRouter? = null,
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,
val appRouter: AppRouter? = null,
) : StateType

View file

@ -0,0 +1,61 @@
package com.tangem.tap.routing
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.navigation.Router
import com.tangem.tap.routing.configurator.AppRouterConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.reflect.KClass
internal class ProxyAppRouter(
private val config: AppRouterConfig,
private val dispatchers: CoroutineDispatcherProvider,
) : AppRouter {
private val routerScope: CoroutineScope
get() = requireNotNull(config.routerScope) {
"Router scope is not set in config"
}
private val innerRouter: Router
get() = requireNotNull(config.componentRouter) {
"Inner router is not set in config"
}
override val stack: List<AppRoute>
get() = requireNotNull(config.stack) {
"Stack is not set in config"
}
override fun push(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
routerScope.launch(dispatchers.mainImmediate) {
innerRouter.push(route, onComplete)
}
}
override fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
routerScope.launch(dispatchers.mainImmediate) {
innerRouter.replaceAll(*routes, onComplete = onComplete)
}
}
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
routerScope.launch(dispatchers.mainImmediate) {
innerRouter.pop(onComplete)
}
}
override fun popTo(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) {
routerScope.launch(dispatchers.mainImmediate) {
innerRouter.popTo(route, onComplete)
}
}
override fun popTo(routeClass: KClass<out AppRoute>, onComplete: (isSuccess: Boolean) -> Unit) {
routerScope.launch(dispatchers.mainImmediate) {
innerRouter.popTo(routeClass, onComplete)
}
}
}

View file

@ -0,0 +1,33 @@
package com.tangem.tap.routing
import android.content.Intent
import androidx.fragment.app.Fragment
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.value.Value
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.navigation.Router
import com.tangem.utils.Provider
internal interface RoutingComponent {
val router: Router
val stack: Value<ChildStack<AppRoute, Child>>
sealed class Child {
data object Initial : Child()
data class LegacyFragment(
val name: String,
val fragmentProvider: Provider<Fragment>,
) : Child()
data class LegacyIntent(val intent: Intent) : Child()
}
interface Factory {
fun create(context: AppComponentContext): RoutingComponent
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tap.routing.configurator
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
import kotlinx.coroutines.CoroutineScope
internal interface AppRouterConfig {
var routerScope: CoroutineScope?
var componentRouter: Router?
var stack: List<AppRoute>?
}

View file

@ -0,0 +1,12 @@
package com.tangem.tap.routing.configurator
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.navigation.Router
import kotlinx.coroutines.CoroutineScope
internal class MutableAppRouterConfig : AppRouterConfig {
override var routerScope: CoroutineScope? = null
override var componentRouter: Router? = null
override var stack: List<AppRoute>? = null
}

View file

@ -0,0 +1,49 @@
package com.tangem.tap.routing.impl
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.backhandler.BackCallback
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.navigation.getOrCreateTyped
import com.tangem.tap.routing.RoutingComponent
import com.tangem.tap.routing.RoutingComponent.Child
import com.tangem.tap.routing.utils.ChildFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class DefaultRoutingComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
private val childFactory: ChildFactory,
) : RoutingComponent, AppComponentContext by context {
private val backCallback = BackCallback(priority = Int.MIN_VALUE, onBack = router::pop)
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
source = navigationProvider.getOrCreateTyped(),
serializer = AppRoute.serializer(),
initialConfiguration = getInitialRoute(),
handleBackButton = false,
childFactory = ::child,
)
init {
backHandler.register(backCallback)
}
private fun getInitialRoute(): AppRoute = AppRoute.Initial
private fun child(route: AppRoute, context: ComponentContext): Child {
return childFactory.createChild(route, { childByContext(context) })
}
@AssistedFactory
interface Factory : RoutingComponent.Factory {
override fun create(context: AppComponentContext): DefaultRoutingComponent
}
}

View file

@ -0,0 +1,164 @@
package com.tangem.tap.routing.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.features.details.DetailsFeatureToggles
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.navigation.StakingRouter
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.tap.features.customtoken.impl.presentation.AddCustomTokenFragment
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
import com.tangem.tap.features.details.ui.details.DetailsFragment
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.main.ui.ModalNotificationBottomSheetFragment
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment
import com.tangem.tap.features.welcome.ui.WelcomeFragment
import com.tangem.tap.routing.RoutingComponent.Child
import com.tangem.utils.Provider
import dagger.hilt.android.scopes.ActivityScoped
import java.util.WeakHashMap
import javax.inject.Inject
@ActivityScoped
@Suppress("LongParameterList")
internal class ChildFactory @Inject constructor(
private val detailsComponentFactory: DetailsComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
private val qrScanningRouter: QrScanningRouter,
private val stakingRouter: StakingRouter,
private val testerRouter: TesterRouter,
private val detailsFeatureToggles: DetailsFeatureToggles,
) {
@Suppress("LongMethod", "CyclomaticComplexMethod")
fun createChild(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child {
componentContexts[route] = contextFactory(route)
return when (route) {
is AppRoute.Initial -> {
Child.Initial
}
is AppRoute.AccessCodeRecovery -> {
route.asFragmentChild(Provider { AccessCodeRecoveryFragment() })
}
is AppRoute.AddCustomToken -> {
route.asFragmentChild(Provider { AddCustomTokenFragment() })
}
is AppRoute.AppCurrencySelector -> {
route.asFragmentChild(Provider { AppCurrencySelectorFragment() })
}
is AppRoute.ModalNotification -> {
route.asFragmentChild(Provider { ModalNotificationBottomSheetFragment() })
}
is AppRoute.SaveWallet -> {
route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() })
}
is AppRoute.Send -> {
route.asFragmentChild(Provider { sendRouter.getEntryFragment() })
}
is AppRoute.AppSettings -> {
route.asFragmentChild(Provider { AppSettingsFragment() })
}
is AppRoute.CardSettings -> {
route.asFragmentChild(Provider { CardSettingsFragment() })
}
is AppRoute.Details -> {
if (detailsFeatureToggles.isRedesignEnabled) {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = DetailsComponent.Params(route.userWalletId),
componentFactory = detailsComponentFactory,
)
} else {
route.asFragmentChild(Provider { DetailsFragment() })
}
}
is AppRoute.DetailsSecurity -> {
route.asFragmentChild(Provider { SecurityModeFragment() })
}
is AppRoute.Disclaimer -> {
route.asFragmentChild(Provider { DisclaimerFragment() })
}
is AppRoute.Home -> {
route.asFragmentChild(Provider { HomeFragment() })
}
is AppRoute.ManageTokens -> {
route.asFragmentChild(Provider { TokensListFragment() })
}
is AppRoute.OnboardingNote -> {
route.asFragmentChild(Provider { OnboardingNoteFragment() })
}
is AppRoute.OnboardingOther -> {
route.asFragmentChild(Provider { OnboardingOtherCardsFragment() })
}
is AppRoute.OnboardingTwins -> {
route.asFragmentChild(Provider { OnboardingTwinsFragment() })
}
is AppRoute.OnboardingWallet -> {
route.asFragmentChild(Provider { OnboardingWalletFragment() })
}
is AppRoute.QrScanning -> {
route.asFragmentChild(Provider { qrScanningRouter.getEntryFragment() })
}
is AppRoute.ReferralProgram -> {
route.asFragmentChild(Provider { ReferralFragment() })
}
is AppRoute.ResetToFactory -> {
route.asFragmentChild(Provider { ResetCardFragment() })
}
is AppRoute.Swap -> {
route.asFragmentChild(Provider { SwapFragment() })
}
is AppRoute.Wallet -> {
route.asFragmentChild(Provider { walletRouter.getEntryFragment() })
}
is AppRoute.WalletConnectSessions -> {
route.asFragmentChild(Provider { WalletConnectFragment() })
}
is AppRoute.CurrencyDetails -> {
route.asFragmentChild(Provider { tokenDetailsRouter.getEntryFragment() })
}
is AppRoute.Welcome -> {
route.asFragmentChild(Provider { WelcomeFragment() })
}
is AppRoute.TesterMenu -> {
Child.LegacyIntent(testerRouter.getEntryIntent())
}
is AppRoute.Staking -> {
route.asFragmentChild(Provider { stakingRouter.getEntryFragment() })
}
}
}
private fun contextProvider(
appRoute: AppRoute,
contextFactory: (route: AppRoute) -> AppComponentContext,
): Provider<AppComponentContext> = Provider {
componentContexts.getOrPut(appRoute) { contextFactory(appRoute) }
}
private companion object {
val componentContexts = WeakHashMap<AppRoute, AppComponentContext>()
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.tap.routing.utils
import androidx.fragment.app.Fragment
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.tap.DecomposeFragment
import com.tangem.tap.routing.RoutingComponent.Child
import com.tangem.utils.Provider
internal fun AppRoute.asFragmentChild(fragmentProvider: Provider<Fragment>): Child {
val provider = Provider {
val bundle = (this as? RouteBundleParams)?.getBundle()
fragmentProvider().apply {
arguments = bundle
}
}
return Child.LegacyFragment(path, provider)
}
internal fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<C, P>> AppRoute.asComponentChild(
contextProvider: Provider<AppComponentContext>,
params: P,
componentFactory: F,
): Child {
val fragmentProvider = Provider {
DecomposeFragment.newInstance(
contextProvider = contextProvider,
params = params,
componentFactory = componentFactory,
)
}
return Child.LegacyFragment(path, fragmentProvider)
}

1
common/routing/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,26 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
android {
namespace = "com.tangem.common.routing"
}
dependencies {
/* Core */
implementation(projects.core.decompose)
/* Domain */
implementation(projects.domain.qrScanning.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.staking.models)
/* Libs - Other */
api(deps.kotlin.serialization)
implementation(deps.androidx.core.ktx)
implementation(deps.timber)
}

View file

@ -0,0 +1,183 @@
package com.tangem.common.routing
import android.os.Bundle
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.SerializableIntent
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.serialization.Serializable
@Serializable
sealed class AppRoute(val path: String) : Route, RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
@Serializable
data object Initial : AppRoute(path = "/initial")
@Serializable
data object Home : AppRoute(path = "/home")
@Serializable
data class Welcome(
val intent: SerializableIntent? = null,
) : AppRoute(path = "/welcome") {
companion object {
const val INITIAL_INTENT_KEY = "intent"
}
}
@Serializable
data class Disclaimer(
val isTosAccepted: Boolean,
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}") {
companion object {
const val IS_TOS_ACCEPTED_KEY = "isTosAccepted"
}
}
@Serializable
data object OnboardingNote : AppRoute(path = "/onboarding/note")
@Serializable
data class OnboardingWallet(
val canSkipBackup: Boolean = true,
) : AppRoute(path = "/onboarding/wallet${if (canSkipBackup) "/skippable" else ""}") {
companion object {
const val CAN_SKIP_BACKUP_KEY = "canSkipBackup"
}
}
@Serializable
data object OnboardingTwins : AppRoute(path = "/onboarding/twins")
@Serializable
data object OnboardingOther : AppRoute(path = "/onboarding/other")
@Serializable
data object Wallet : AppRoute(path = "/wallet")
@Serializable
data class CurrencyDetails(
val userWalletId: UserWalletId,
val currency: CryptoCurrency,
) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}") {
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
const val CRYPTO_CURRENCY_KEY = "currency"
}
}
@Serializable
data class Send(
val userWalletId: UserWalletId,
val currency: CryptoCurrency,
val transactionId: String? = null,
val amount: String? = null,
val tag: String? = null,
val destinationAddress: String? = null,
) : AppRoute(
path = "/send/${userWalletId.stringValue}/${currency.id.value}?" +
"&$transactionId" +
"&$amount" +
"&$tag" +
"&$destinationAddress",
) {
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
const val CRYPTO_CURRENCY_KEY = "currency"
const val TRANSACTION_ID_KEY = "transactionId"
const val AMOUNT_KEY = "amount"
const val TAG_KEY = "tag"
const val DESTINATION_ADDRESS_KEY = "destinationAddress"
}
}
@Serializable
data class Details(
val userWalletId: UserWalletId,
) : AppRoute(path = "/details/${userWalletId.stringValue}")
@Serializable
data object DetailsSecurity : AppRoute(path = "/details/security")
@Serializable
data object CardSettings : AppRoute(path = "/card_settings")
@Serializable
data object AppSettings : AppRoute(path = "/app_settings")
@Serializable
data object ResetToFactory : AppRoute(path = "/reset_to_factory")
@Serializable
data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery")
@Serializable
data object ManageTokens : AppRoute(path = "/manage_tokens")
@Serializable
data object AddCustomToken : AppRoute(path = "/add_custom_token")
@Serializable
data object WalletConnectSessions : AppRoute(path = "/wallet_connect_sessions")
@Serializable
data class QrScanning(
val source: SourceType,
val networkName: String? = null,
) : AppRoute(path = "/qr_scanning") {
companion object {
const val SOURCE_KEY = "source"
const val NETWORK_KEY = "networkName"
}
}
@Serializable
data object ReferralProgram : AppRoute(path = "/referral_program")
@Serializable
data class Swap(
val currency: CryptoCurrency,
) : AppRoute(path = "/swap") {
companion object {
const val CURRENCY_BUNDLE_KEY = "currency"
}
}
@Serializable
data object TesterMenu : AppRoute(path = "/tester_menu")
@Serializable
data object SaveWallet : AppRoute(path = "/save_wallet")
@Serializable
data object AppCurrencySelector : AppRoute(path = "/app_currency_selector")
@Serializable
data object ModalNotification : AppRoute(path = "/modal_notification")
@Serializable
data class Staking(
val userWalletId: UserWalletId,
val cryptoCurrencyId: CryptoCurrency.ID,
val yield: Yield,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}") {
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
const val CRYPTO_CURRENCY_ID_KEY = "cryptoCurrencyId"
const val YIELD_KEY = "yield"
}
}
}

View file

@ -0,0 +1,58 @@
package com.tangem.common.routing
import kotlin.reflect.KClass
/**
* Interface for a router in the application.
* It provides methods for navigating through the application.
*
* Same as [com.tangem.core.decompose.navigation.Router] but without Decompose dependency.
*
* ***Must be removed after Decompose migration.***
*/
interface AppRouter {
/**
* The current navigation stack.
*/
val stack: List<AppRoute>
/**
* Pushes a new route to the navigation stack.
*
* @param route The route to push.
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun push(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
/**
* Replaces ***all*** routes in the navigation stack with the specified [routes].
*
* @param routes The routes to replace the current stack with.
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
/**
* Pops the top route from the navigation stack.
*
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun pop(onComplete: (isSuccess: Boolean) -> Unit = {})
/**
* Pops routes from the navigation stack until the specified [route] is found.
*
* @param route The route to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun popTo(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit = {})
/**
* Pops routes from the navigation stack until the ***first*** specified [routeClass] is found.
*
* @param routeClass The route class to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
fun popTo(routeClass: KClass<out AppRoute>, onComplete: (isSuccess: Boolean) -> Unit = {})
}

View file

@ -0,0 +1,108 @@
package com.tangem.common.routing.bundle
import android.os.Bundle
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.SerializersModule
@ExperimentalSerializationApi
internal class BundleDecoder(
private val bundle: Bundle,
private val elementsCount: Int = -1,
private val isInitializer: Boolean = true,
override val serializersModule: SerializersModule,
) : AbstractDecoder() {
private var index = -1
private var elementKey: String? = null
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
if (++index >= elementsCount) {
return CompositeDecoder.DECODE_DONE
}
elementKey = descriptor.getElementName(index)
return index
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
val b = if (isInitializer) {
bundle
} else {
requireNotNull(bundle.getBundle(elementKey)) {
"Bundle is missing for key $elementKey while decoding"
}
}
val count = when (descriptor.kind) {
StructureKind.MAP,
StructureKind.LIST,
-> b.getInt("\$size")
else -> descriptor.elementsCount
}
return BundleDecoder(
bundle = b,
elementsCount = count,
isInitializer = false,
serializersModule = serializersModule,
)
}
override fun endStructure(descriptor: SerialDescriptor) {
/* no-op */
}
override fun decodeBoolean(): Boolean {
return bundle.getBoolean(elementKey)
}
override fun decodeByte(): Byte {
return bundle.getByte(elementKey)
}
override fun decodeChar(): Char {
return bundle.getChar(elementKey)
}
override fun decodeDouble(): Double {
return bundle.getDouble(elementKey)
}
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
return bundle.getInt(elementKey)
}
override fun decodeFloat(): Float {
return bundle.getFloat(elementKey)
}
override fun decodeInt(): Int {
return bundle.getInt(elementKey)
}
override fun decodeLong(): Long {
return bundle.getLong(elementKey)
}
override fun decodeNotNullMark(): Boolean {
return bundle.containsKey(elementKey)
}
override fun decodeNull(): Nothing? {
return null
}
override fun decodeShort(): Short {
return bundle.getShort(elementKey)
}
override fun decodeString(): String {
return requireNotNull(bundle.getString(elementKey)) {
"String is missing for key $elementKey while decoding"
}
}
}

View file

@ -0,0 +1,107 @@
package com.tangem.common.routing.bundle
import android.os.Bundle
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.StructureKind
import kotlinx.serialization.encoding.AbstractEncoder
import kotlinx.serialization.encoding.CompositeEncoder
import kotlinx.serialization.modules.SerializersModule
@ExperimentalSerializationApi
internal class BundleEncoder(
private val bundle: Bundle,
private val parentBundle: Bundle? = null,
private val keyInParent: String? = null,
private val isInitializer: Boolean = true,
override val serializersModule: SerializersModule,
) : AbstractEncoder() {
private var elementKey: String? = null
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
elementKey = descriptor.getElementName(index)
return super.encodeElement(descriptor, index)
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
return if (isInitializer) {
BundleEncoder(
bundle = bundle,
parentBundle = null,
keyInParent = elementKey,
isInitializer = false,
serializersModule = serializersModule,
)
} else {
BundleEncoder(
bundle = Bundle(),
parentBundle = bundle,
keyInParent = elementKey,
isInitializer = false,
serializersModule = serializersModule,
)
}
}
override fun endStructure(descriptor: SerialDescriptor) {
if (descriptor.kind in arrayOf(StructureKind.LIST, StructureKind.MAP)) {
val size = elementKey?.toIntOrNull()?.let { it + 1 } ?: 0
bundle.putInt("\$size", size)
}
if (keyInParent.isNullOrBlank()) {
return
}
parentBundle?.putBundle(keyInParent, bundle)
}
override fun encodeBoolean(value: Boolean) {
bundle.putBoolean(elementKey, value)
}
override fun encodeByte(value: Byte) {
bundle.putByte(elementKey, value)
}
override fun encodeChar(value: Char) {
bundle.putChar(elementKey, value)
}
override fun encodeDouble(value: Double) {
bundle.putDouble(elementKey, value)
}
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) {
bundle.putInt(elementKey, index)
}
override fun encodeFloat(value: Float) {
bundle.putFloat(elementKey, value)
}
override fun encodeInt(value: Int) {
bundle.putInt(elementKey, value)
}
override fun encodeLong(value: Long) {
bundle.putLong(elementKey, value)
}
override fun encodeNull() {
/* no-op */
}
override fun encodeShort(value: Short) {
bundle.putShort(elementKey, value)
}
override fun encodeString(value: String) {
bundle.putString(elementKey, value)
}
override fun encodeNotNullMark() {
/* no-op */
}
}

View file

@ -0,0 +1,60 @@
package com.tangem.common.routing.bundle
import android.os.Bundle
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
val defaultSerializersModule: SerializersModule = EmptySerializersModule()
/**
* Deserialize this bundle into an object of type [T].
*
* @receiver [Bundle] to deserialize.
* @param deserializer [DeserializationStrategy] of the [T] class.
*
* @return Object of type T deserialized from bundle.
*/
@OptIn(ExperimentalSerializationApi::class)
fun <T> Bundle.unbundle(
deserializer: DeserializationStrategy<T>,
serializersModule: SerializersModule = defaultSerializersModule,
): T {
val decoder = BundleDecoder(
bundle = this,
elementsCount = -1,
isInitializer = true,
serializersModule = serializersModule,
)
return deserializer.deserialize(decoder)
}
/**
* Serialize [T] into a bundle.
*
* @receiver Object to serialize.
* @param serializer [SerializationStrategy] of the [T] class.
*
* @return bundle serialized from value
*/
@OptIn(ExperimentalSerializationApi::class)
fun <T> T.bundle(
serializer: SerializationStrategy<T>,
serializersModule: SerializersModule = defaultSerializersModule,
): Bundle {
val bundle = Bundle(serializer.descriptor.elementsCount)
val encoder = BundleEncoder(
bundle = bundle,
parentBundle = null,
keyInParent = null,
isInitializer = true,
serializersModule = serializersModule,
)
serializer.serialize(encoder, value = this)
return bundle
}

View file

@ -0,0 +1,8 @@
package com.tangem.common.routing.bundle
import android.os.Bundle
interface RouteBundleParams {
fun getBundle(): Bundle
}

View file

@ -0,0 +1,24 @@
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)
}
}
}
}

View file

@ -0,0 +1,51 @@
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,
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
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.common.routing.utils
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
/**
* Pops routes from the navigation stack until the specified route [R] is found.
*
* ***Must be removed after Decompose migration.***
*
* @param R The route to pop to.
* @param onComplete The callback to be invoked when the operation is complete.
*/
inline fun <reified R : AppRoute> AppRouter.popTo(noinline onComplete: (isSuccess: Boolean) -> Unit = {}) {
popTo(R::class, onComplete)
}

View file

@ -1,25 +0,0 @@
package com.tangem.core.navigation
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.rekotlin.Action
import java.lang.ref.WeakReference
sealed class NavigationAction : Action {
data class NavigateTo(
val screen: AppScreen,
val fragmentShareTransition: FragmentShareTransition? = null,
val addToBackstack: Boolean = true,
val bundle: Bundle? = null,
) : NavigationAction()
data class PopBackTo(val screen: AppScreen? = null, val inclusive: Boolean = false) : NavigationAction()
data class OpenDocument(val url: Uri) : NavigationAction()
data class ActivityCreated(val activity: WeakReference<AppCompatActivity>) : NavigationAction()
data class ActivityDestroyed(val activity: WeakReference<AppCompatActivity>) : NavigationAction()
}

View file

@ -1,39 +0,0 @@
package com.tangem.core.navigation
import androidx.appcompat.app.AppCompatActivity
import org.rekotlin.StateType
import java.lang.ref.WeakReference
data class NavigationState(
val backStack: List<AppScreen> = emptyList(),
val activity: WeakReference<AppCompatActivity>? = null,
) : StateType
enum class AppScreen(val isDialogFragment: Boolean = false) {
Home,
Disclaimer,
OnboardingNote,
OnboardingWallet,
OnboardingTwins,
OnboardingOther,
Wallet,
WalletDetails,
Send(isDialogFragment = true),
Details,
DetailsSecurity,
CardSettings,
AppSettings,
ResetToFactory,
AccessCodeRecovery,
ManageTokens,
AddCustomToken,
WalletConnectSessions,
QrScanning,
ReferralProgram,
Swap,
Welcome,
SaveWallet(isDialogFragment = true),
AppCurrencySelector,
ModalNotification(isDialogFragment = true),
Staking,
}

View file

@ -1,16 +0,0 @@
package com.tangem.core.navigation
/**
* Navigation controller that based on redux actions
*
[REDACTED_AUTHOR]
*/
interface ReduxNavController {
/** Navigate by [action] */
fun navigate(action: NavigationAction)
fun popBackStack(screen: AppScreen? = null)
fun getBackStack(): List<AppScreen>
}

View file

@ -1,18 +1,8 @@
package com.tangem.core.navigation
import android.view.View
import androidx.transition.TransitionSet
import java.lang.ref.WeakReference
/**
[REDACTED_AUTHOR]
*/
data class FragmentShareTransition(
val shareElements: List<ShareElement>,
val enterTransitionSet: TransitionSet,
val exitTransitionSet: TransitionSet,
)
/**
* For ease of use, the name is used as transitionName\name into the FragmentTransaction.addSharedElement
*/

View file

@ -1,5 +1,6 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@ -7,4 +8,6 @@ dependencies {
api(deps.kotlin.coroutines)
api(deps.arrow.core)
api(deps.arrow.fx)
implementation(deps.kotlin.serialization)
}

View file

@ -0,0 +1,21 @@
package com.tangem.domain.core.serialization
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.math.BigDecimal
internal object BigDecimalSerializer : KSerializer<BigDecimal> {
override val descriptor = PrimitiveSerialDescriptor(serialName = "BigDecimal", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: BigDecimal) {
encoder.encodeString(value.toString())
}
override fun deserialize(decoder: Decoder): BigDecimal {
return BigDecimal(decoder.decodeString())
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.core.serialization
import kotlinx.serialization.Serializable
import java.math.BigDecimal
typealias SerializedBigDecimal = @Serializable(with = BigDecimalSerializer::class) BigDecimal

View file

@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@ -10,10 +11,10 @@ android {
dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.arrow.core)
implementation(deps.kotlin.serialization)
api(projects.domain.staking.models)
api(projects.domain.core)
implementation(deps.kotlin.serialization)
implementation(projects.domain.tokens.models)
}

1
domain/staking/models/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,11 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
dependencies {
implementation(projects.domain.core)
implementation(deps.kotlin.serialization)
}

View file

@ -1,54 +1,64 @@
package com.tangem.domain.staking.model
import java.io.Serializable
import java.math.BigDecimal
import com.tangem.domain.core.serialization.SerializedBigDecimal
import kotlinx.serialization.Serializable
@Serializable
data class Yield(
val id: String,
val token: Token,
val tokens: List<Token>,
val args: Args,
val status: Status,
val apy: BigDecimal,
val apy: SerializedBigDecimal,
val rewardRate: Double,
val rewardType: RewardType,
val metadata: Metadata,
val validators: List<Validator>,
val isAvailable: Boolean,
) : Serializable {
) {
@Serializable
data class Status(
val enter: Boolean,
val exit: Boolean?,
) : Serializable
)
@Serializable
data class Args(
val enter: Enter,
val exit: Enter?,
) : Serializable {
) {
@Serializable
data class Enter(
val addresses: Addresses,
val args: Map<String, AddressArgument>,
) : Serializable {
) {
@Serializable
data class Addresses(
val address: AddressArgument,
val additionalAddresses: Map<String, AddressArgument>? = null,
) : Serializable
)
}
}
@Serializable
data class Validator(
val address: String,
val status: String,
val name: String,
val image: String?,
val website: String?,
val apr: BigDecimal?,
val apr: SerializedBigDecimal?,
val commission: Double?,
val stakedBalance: String?,
val votingPower: Double?,
val preferred: Boolean,
) : Serializable
)
@Serializable
data class Metadata(
val name: String,
val logoUri: String,
@ -67,14 +77,17 @@ data class Yield(
val supportsMultipleValidators: Boolean,
val revshare: Enabled,
val fee: Enabled,
) : Serializable {
) {
@Serializable
data class Period(
val days: Int,
) : Serializable
)
@Serializable
data class Enabled(
val enabled: Boolean,
) : Serializable
)
}
enum class RewardType {
@ -84,6 +97,7 @@ data class Yield(
}
}
@Serializable
data class Token(
val name: String,
val network: NetworkType,
@ -93,7 +107,7 @@ data class Token(
val coinGeckoId: String?,
val logoURI: String?,
val isPoints: Boolean?,
) : Serializable {
) {
enum class NetworkType {
AVALANCHE_C,
AVALANCHE_ATOMIC,
@ -165,9 +179,10 @@ data class Token(
}
}
@Serializable
data class AddressArgument(
val required: Boolean,
val network: String? = null,
val minimum: Double? = null,
val maximum: Double? = null,
) : Serializable
)

View file

@ -1,7 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("kotlin-parcelize")
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@ -10,6 +10,7 @@ android {
}
dependencies {
implementation(deps.kotlin.serialization)
implementation(projects.domain.txhistory.models)
implementation(projects.core.analytics.models)
implementation(deps.tangem.blockchain) {

View file

@ -1,7 +1,6 @@
package com.tangem.domain.tokens.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
/**
* Represents a generic cryptocurrency.
@ -14,8 +13,8 @@ import kotlinx.parcelize.Parcelize
* @property iconUrl Optional URL of the cryptocurrency icon. `null` if not found.
* @property isCustom Indicates whether the currency is a custom user-added currency or not.
*/
@Parcelize
sealed class CryptoCurrency : Parcelable {
@Serializable
sealed class CryptoCurrency {
abstract val id: ID
abstract val network: Network
@ -28,6 +27,7 @@ sealed class CryptoCurrency : Parcelable {
/**
* Represents a native coin in the blockchain network.
*/
@Serializable
data class Coin(
override val id: ID,
override val network: Network,
@ -48,6 +48,7 @@ sealed class CryptoCurrency : Parcelable {
*
* @property contractAddress Address of the contract managing the token.
*/
@Serializable
data class Token(
override val id: ID,
override val network: Network,
@ -75,12 +76,12 @@ sealed class CryptoCurrency : Parcelable {
* @property rawCurrencyId Represents not unique currency ID from the blockchain network. `null` if
* its ID of the custom token.
*/
@Parcelize
@Serializable
data class ID(
private val prefix: Prefix,
private val body: Body,
private val suffix: Suffix,
) : Parcelable {
) {
val value: String
get() = buildString {
@ -121,12 +122,13 @@ sealed class CryptoCurrency : Parcelable {
*
* The body can be either a raw network ID or a raw network ID with a network derivation path.
*/
@Parcelize
sealed class Body : Parcelable {
@Serializable
sealed class Body {
/** The value of the body. */
abstract val value: String
@Serializable
/** Represents a raw network ID. */
data class NetworkId(val rawId: String) : Body() {
override val value: String get() = rawId
@ -137,6 +139,7 @@ sealed class CryptoCurrency : Parcelable {
*
* Should be used for a cryptocurrencies with custom derivation path.
* */
@Serializable
data class NetworkIdWithDerivationPath(
val rawId: String,
val derivationPath: String,
@ -155,13 +158,14 @@ sealed class CryptoCurrency : Parcelable {
*
* The suffix can either be a raw ID or a contract address.
*/
@Parcelize
sealed class Suffix : Parcelable {
@Serializable
sealed class Suffix {
/** The value of the suffix, which could be either a raw ID or a contract address. */
abstract val value: String
/** Represents a raw ID suffix. */
@Serializable
data class RawID(val rawId: String, val contractAddress: String? = null) : Suffix() {
override val value: String
get() = buildString {
@ -174,6 +178,7 @@ sealed class CryptoCurrency : Parcelable {
}
/** Represents a contract address suffix. */
@Serializable
data class ContractAddress(val contractAddress: String) : Suffix() {
override val value: String get() = contractAddress
}
@ -183,11 +188,11 @@ sealed class CryptoCurrency : Parcelable {
return "ID(value='$value')"
}
private companion object {
companion object {
// should use delimiters that could be used in URL not like path or query delimiters
const val PREFIX_DELIMITER = '_'
const val SUFFIX_DELIMITER = ';'
const val DERIVATION_PATH_DELIMITER = 'd'
private const val PREFIX_DELIMITER = '_'
private const val SUFFIX_DELIMITER = ';'
private const val DERIVATION_PATH_DELIMITER = 'd'
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.domain.tokens.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
/**
* Represents a blockchain network, identified by a unique ID, a human-readable name, and its standard type.
@ -20,7 +19,7 @@ import kotlinx.parcelize.Parcelize
* that cannot be represented in a fiat currency.
* (For those blockchains that have FeeResource instead of a standard type of fee)
*/
@Parcelize
@Serializable
data class Network(
val id: ID,
val backendId: String,
@ -30,7 +29,7 @@ data class Network(
val isTestnet: Boolean,
val standardType: StandardType,
val hasFiatFeeRate: Boolean,
) : Parcelable {
) {
init {
require(name.isNotBlank()) { "Network name must not be blank" }
@ -42,8 +41,8 @@ data class Network(
* @property value The string representation of the network ID.
*/
@JvmInline
@Parcelize
value class ID(val value: String) : Parcelable {
@Serializable
value class ID(val value: String) {
init {
require(value.isNotBlank()) { "Network ID must not be blank" }
@ -56,8 +55,8 @@ data class Network(
* This class represents such paths in a generic manner, allowing for predefined card-based paths,
* custom paths, or even no derivation path at all.
*/
@Parcelize
sealed class DerivationPath : Parcelable {
@Serializable
sealed class DerivationPath {
/** The actual derivation path value, if any. */
abstract val value: String?
@ -67,6 +66,7 @@ data class Network(
*
* @property value The derivation path string.
*/
@Serializable
data class Card(override val value: String) : DerivationPath()
/**
@ -74,12 +74,14 @@ data class Network(
*
* @property value The derivation path string.
*/
@Serializable
data class Custom(override val value: String) : DerivationPath()
/**
* Represents a lack of derivation path.
*/
object None : DerivationPath() {
@Serializable
data object None : DerivationPath() {
override val value: String? get() = null
}
}
@ -93,31 +95,36 @@ data class Network(
*
* @property name The human-readable name of the standard type.
*/
@Parcelize
sealed class StandardType : Parcelable {
@Serializable
sealed class StandardType {
abstract val name: String
/** Represents the ERC20 token standard, common on the Ethereum network. */
object ERC20 : StandardType() {
@Serializable
data object ERC20 : StandardType() {
override val name: String get() = "ERC20"
}
/** Represents the TRC20 token standard, common on the TRON network. */
object TRC20 : StandardType() {
@Serializable
data object TRC20 : StandardType() {
override val name: String get() = "TRC20"
}
/** Represents the BEP20 token standard, common on the Binance Smart Chain network. */
object BEP20 : StandardType() {
@Serializable
data object BEP20 : StandardType() {
override val name: String get() = "BEP20"
}
/** Represents the BEP2 token standard, common on the Binance Chain network. */
object BEP2 : StandardType() {
@Serializable
data object BEP2 : StandardType() {
override val name: String get() = "BEP2"
}
/** Represents a network that does not adhere to a predefined standard type. */
@Serializable
data class Unspecified(override val name: String) : StandardType()
}
}

View file

@ -1,5 +1,6 @@
plugins {
alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.serialization)
id("configuration")
}
@ -11,4 +12,8 @@ dependencies {
// region Domain modules
implementation(project(":domain:models"))
// endregion
// region Other libraries
implementation(deps.kotlin.serialization)
// endregion
}

View file

@ -2,9 +2,10 @@ package com.tangem.domain.wallets.models
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import java.io.Serializable
import kotlinx.serialization.Serializable
data class UserWalletId(val stringValue: String) : Serializable {
@Serializable
data class UserWalletId(val stringValue: String) {
val value = stringValue.hexToBytes()

View file

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

View file

@ -1,13 +0,0 @@
package com.tangem.features.details
import androidx.fragment.app.Fragment
interface DetailsEntryPoint {
fun entryFragment(): Fragment
companion object {
const val USER_WALLET_ID_KEY = "user_wallet_id"
}
}

View file

@ -1,17 +1,15 @@
package com.tangem.features.details.component
import androidx.compose.material3.SnackbarHostState
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
interface DetailsComponent : ComposableContentComponent {
val snackbarHostState: SnackbarHostState
interface Factory : ComponentFactory<DetailsComponent, Params> {
interface Factory {
fun create(context: AppComponentContext, params: Params): DetailsComponent
override fun create(context: AppComponentContext, params: Params): DetailsComponent
}
data class Params(

Some files were not shown because too many files have changed in this diff Show more