From d65b4039daf7987430e74e963265c2145ab6c755 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 28 Apr 2025 09:56:00 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../java/com/tangem/tap/DecomposeFragment.kt | 65 ---- .../main/java/com/tangem/tap/MainActivity.kt | 201 +--------- .../tap/common/extensions/Navigation.kt | 60 --- .../com/tangem/tap/common/extensions/Store.kt | 12 +- .../com/tangem/tap/common/redux/AppState.kt | 1 - .../common/redux/NotificationsMiddleware.kt | 141 ------- .../tap/common/redux/global/GlobalAction.kt | 7 - .../com/tangem/tap/routing/ProxyAppRouter.kt | 7 +- .../tap/routing/component/RoutingComponent.kt | 9 - .../component/impl/DefaultRoutingComponent.kt | 37 +- .../routing/toggle/RoutingFeatureToggles.kt | 14 - .../tangem/tap/routing/utils/ChildFactory.kt | 364 +----------------- .../tangem/tap/routing/utils/RouteMappers.kt | 34 -- app/src/main/res/layout/activity_main.xml | 15 - .../configs/feature_toggles_config.json | 4 - 15 files changed, 26 insertions(+), 945 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/DecomposeFragment.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt delete mode 100644 app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt delete mode 100644 app/src/main/res/layout/activity_main.xml diff --git a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt deleted file mode 100644 index 3136ae77dd..0000000000 --- a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.tangem.tap - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.core.ui.screen.ComposeFragment -import com.tangem.utils.Provider -import dagger.hilt.android.AndroidEntryPoint -import java.util.WeakHashMap -import javax.inject.Inject - -@AndroidEntryPoint -internal class DecomposeFragment : ComposeFragment() { - - @Inject - override lateinit var uiDependencies: UiDependencies - - private val component by lazy(LazyThreadSafetyMode.NONE) { - val tag = requireArguments().getString(TAG_KEY) - val builder = componentsBuilders[tag] - - requireNotNull(builder?.build()) { - "Component builder is not set, call newInstance() for DecomposeFragment creation first." - } - } - - @Composable - override fun ScreenContent(modifier: Modifier) { - component.Content(modifier) - } - - private class ComponentBuilder>( - private val contextProvider: Provider, - private val params: P, - private val componentFactory: F, - ) { - - fun build(): C = componentFactory.create(contextProvider(), params) - } - - companion object { - - private const val TAG_KEY = "tag" - - private val componentsBuilders = WeakHashMap>() - - fun > newInstance( - tag: String, - contextProvider: Provider, - params: P, - componentFactory: F, - ): Fragment { - this@Companion.componentsBuilders[tag] = ComponentBuilder(contextProvider, params, componentFactory) - - return DecomposeFragment().apply { - this.arguments = bundleOf(TAG_KEY to tag) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 081728b21c..66df0f2d73 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -12,7 +12,6 @@ import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode @@ -20,19 +19,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse -import by.kirich1409.viewbindingdelegate.viewBinding -import com.arkivanov.decompose.value.subscribe -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 @@ -41,12 +32,6 @@ import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.navigation.email.EmailSender import com.tangem.core.ui.UiDependencies -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.message.EventMessageEffect -import com.tangem.core.ui.message.SnackbarMessage -import com.tangem.core.ui.res.TangemColorPalette -import com.tangem.core.ui.res.TangemTheme import com.tangem.data.balancehiding.DefaultDeviceFlipDetector import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.domain.apptheme.model.AppThemeMode @@ -70,11 +55,8 @@ import com.tangem.sdk.api.TangemSdkManager import com.tangem.tap.common.ActivityResultCallbackHolder import com.tangem.tap.common.DialogManager import com.tangem.tap.common.OnActivityResultCallback -import com.tangem.tap.common.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.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.features.intentHandler.IntentProcessor import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler @@ -85,11 +67,8 @@ import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphAction import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.configurator.AppRouterConfig -import com.tangem.tap.routing.toggle.RoutingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler -import com.tangem.wallet.R -import com.tangem.wallet.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -102,7 +81,6 @@ lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService internal var lockUserWalletsTimer: LockUserWalletsTimer? = null private set -var notificationsHandler: NotificationsHandler? = null private val coroutineContext: CoroutineContext get() = SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("scope") @@ -114,7 +92,7 @@ val mainScope = CoroutineScope(mainCoroutineContext) @Suppress("LargeClass") @AndroidEntryPoint -class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { +class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { @Inject lateinit var appStateHolder: AppStateHolder @@ -187,9 +165,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var dispatchers: CoroutineDispatcherProvider - @Inject - internal lateinit var routingFeatureToggles: RoutingFeatureToggles - @Inject internal lateinit var uiDependencies: UiDependencies @@ -203,9 +178,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac // TODO: fixme: inject through DI private val intentProcessor: IntentProcessor = IntentProcessor() - private var snackbar: Snackbar? = null private val dialogManager = DialogManager() - private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind) private val onActivityResultCallbacks = mutableListOf() @@ -242,13 +215,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac installActivityDependencies() observeAppThemeModeUpdates() - if (routingFeatureToggles.isNavigationRefactoringEnabled) { - setRootContent() - } else { - setContentView(R.layout.activity_main) - installRouting() - installEventMessageEffect() - } + setRootContent() initContent() @@ -265,33 +232,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lifecycle.addObserver(defaultDeviceFlipDetector) } - private fun installEventMessageEffect() { - binding.composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - binding.composeView.setContent { - TangemTheme( - activity = this, - uiDependencies = uiDependencies, - overrideSystemBarColors = false, - ) { - EventMessageEffect( - onShowSnackbar = { message, _ -> - showSnackbar( - text = message.message.resolveReference(resources), - length = when (message.duration) { - SnackbarMessage.Duration.Short -> Snackbar.LENGTH_SHORT - SnackbarMessage.Duration.Long -> Snackbar.LENGTH_LONG - SnackbarMessage.Duration.Indefinite -> Snackbar.LENGTH_INDEFINITE - }, - buttonTitle = message.actionLabel?.resolveReference(resources), - action = message.action, - onDismiss = message.onDismissRequest, - ) - }, - ) - } - } - } - private fun setRootContent() { // for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy // doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it @@ -306,54 +246,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } - private fun installRouting() { - // for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy - // doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it - // we try to init component with previous stack - val routingComponent = routingComponentFactory.create( - context = rootComponentContext, - initialStack = appRouterConfig.stack, - ) - - appRouterConfig.routerScope = mainScope - appRouterConfig.componentRouter = routingComponent.router - appRouterConfig.snackbarHandler = this - - routingComponent.stack.subscribe(lifecycle.asEssentyLifecycle()) { childStack -> - val stack = childStack.backStack - .plus(childStack.active) - .map { it.configuration } - - if (stack == appRouterConfig.stack) return@subscribe - - appRouterConfig.stack = stack - - if (stack.size == 1) { - supportFragmentManager.run { - val activeChildName = stack.first().path - - (0 until backStackEntryCount) - .mapNotNull { getBackStackEntryAt(it).name } - .filter { it != activeChildName } - .forEach { - popBackStackImmediate(it, POP_BACK_STACK_INCLUSIVE) - } - } - } - - 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) - } - is RoutingComponent.Child.ComposableComponent -> error("Unsupported child: $child") - } - } - } - private fun installActivityDependencies() { cardSdkOwner.register(activity = this) tangemSdkManager = injectedTangemSdkManager @@ -429,17 +321,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun onResume() { super.onResume() - - if (!routingFeatureToggles.isNavigationRefactoringEnabled) { - // TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop - notificationsHandler = NotificationsHandler(binding.fragmentContainer) - } - navigateToInitialScreenIfNeeded(intent) } override fun onStop() { - notificationsHandler = null dialogManager.onStop() super.onStop() } @@ -472,31 +357,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme() } - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - - /* - * We need to manually change the background color of the activity when the UI mode changes to prevent - * flickering when navigating between fragments. - * - - * `android:configChanges="uiMode"` is set in the manifest. - * */ - if (routingFeatureToggles.isNavigationRefactoringEnabled.not()) { - updateAppBackground() - } - } - - private fun updateAppBackground() { - val backgroundColor = if (isDarkTheme()) { - TangemColorPalette.Dark6 - } else { - TangemColorPalette.White - } - - findViewById(R.id.fragment_container).setBackgroundColor(backgroundColor.toArgb()) - } - private fun isDarkTheme(): Boolean { return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> true @@ -518,29 +378,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } - override fun showSnackbar(@StringRes text: Int, length: Int, @StringRes buttonTitle: Int?, action: (() -> Unit)?) { - showSnackbar( - getString(text), - length, - buttonTitle?.let(::getString), - action = { action?.invoke() }, - ) - } - - override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) { - showSnackbar( - text = text.resolveReference(resources), - length = length, - buttonTitle = buttonTitle?.resolveReference(resources), - action = { action?.invoke() }, - ) - } - - override fun dismissSnackbar() { - snackbar?.dismiss() - snackbar = null - } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) @@ -568,40 +405,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac return if (result) super.dispatchTouchEvent(event) else false } - private fun showSnackbar( - text: String, - length: Int, - buttonTitle: String?, - action: (() -> Unit)? = null, - onDismiss: () -> Unit = {}, - ) { - if (snackbar != null) return - - snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply { - val textColor = getColor(R.color.text_primary_2) - - setBackgroundTint(getColor(R.color.button_primary)) - setActionTextColor(textColor) - setTextColor(textColor) - - if (buttonTitle != null && action != null) { - setAction(buttonTitle, { action() }) - } - - addCallback( - object : BaseTransientBottomBar.BaseCallback() { - override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { - onDismiss() - snackbar = null - removeCallback(this) - } - }, - ) - } - - snackbar?.show() - } - private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) { val backStack = appRouterConfig.stack ?: emptyList() // TODO move inital navigation to navigation component ([REDACTED_JIRA]) diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt deleted file mode 100644 index 243a2f8770..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.tap.common.extensions - -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 FragmentManager.showFragmentAllowingStateLoss(name: String, fragmentProvider: Provider) { - if (backStackEntryCount > 0) { - val currentFragmentName = getBackStackEntryAt(backStackEntryCount - 1).name - - if (name == currentFragmentName) { - Timber.i("Fragment $name is already at the top of the stack") - return - } - } - - Timber.i("Showing $name route") - - val isPoppedBack = popBackStackImmediate(name, 0) - - if (!isPoppedBack) { - val fragment = fragmentProvider() - - if (fragment is DialogFragment) { - fragment.showDialog(fragmentManager = this, name) - } else { - fragment.showFragment(fragmentManager = this, name) - } - - Timber.i("Route $name is shown") - } else { - Timber.i("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 Fragment.showFragment(fragmentManager: FragmentManager, name: String) { - val transaction = fragmentManager.beginTransaction() - - transaction.replace(R.id.fragment_container, this, name) - transaction.addToBackStack(name) - - transaction.commitAllowingStateLoss() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 919c21972d..c865434607 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -1,6 +1,8 @@ package com.tangem.tap.common.extensions import com.tangem.common.routing.AppRouter +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.redux.StateDialog import com.tangem.domain.wallets.models.UserWallet @@ -43,20 +45,16 @@ suspend fun Store.onUserWalletSelected(userWallet: UserWallet) { state.globalState.tapWalletManager.onWalletSelected(userWallet) } -fun Store<*>.dispatchErrorNotification(error: TapError) { - dispatchOnMain(GlobalAction.ShowErrorNotification(error)) -} - /** * @param fatal used to indicate errors that should not normally occur */ -fun Store<*>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) { +fun Store.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) { val prefix = if (fatal) "FATAL ERROR: " else "DEBUG ERROR: " dispatchDebugErrorNotification(TapError.CustomError("$prefix $message")) } -fun Store<*>.dispatchDebugErrorNotification(error: TapError) { - dispatchOnMain(GlobalAction.DebugShowErrorNotification(error)) +fun Store.dispatchDebugErrorNotification(error: TapError) { + inject(DaggerGraphState::uiMessageSender).send(SnackbarMessage(stringReference(error.message ?: "debug error"))) } fun Store<*>.dispatchDialogShow(dialog: StateDialog) { diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index e1814f72c0..3df1fae79b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -31,7 +31,6 @@ data class AppState( fun getMiddleware(): List> { return listOf( logMiddleware, - notificationsMiddleware, GlobalMiddleware.handler, HomeMiddleware.handler, DetailsMiddleware().detailsMiddleware, diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt deleted file mode 100644 index 1b862e7ed4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.tangem.tap.common.redux - -import android.content.Context -import android.widget.Toast -import androidx.coordinatorlayout.widget.CoordinatorLayout -import com.google.android.material.snackbar.Snackbar -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.domain.ArgError -import com.tangem.tap.domain.MultiMessageError -import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.assembleErrors -import com.tangem.tap.notificationsHandler -import com.tangem.wallet.BuildConfig -import com.tangem.wallet.R -import org.rekotlin.Action -import org.rekotlin.Middleware -import java.lang.ref.WeakReference - -class NotificationsHandler(coordinatorLayout: CoordinatorLayout) { - private val basicCoordinatorLayout = WeakReference(coordinatorLayout) - private var baseLayout = basicCoordinatorLayout - - private fun showNotification(message: String) { - baseLayout.get()?.let { layout -> - Snackbar.make(layout, message, Snackbar.LENGTH_LONG) - .also { snackbar -> snackbar.show() } - } - } - - private fun showDebugNotification(message: String) { - baseLayout.get()?.let { layout -> - Snackbar.make(layout, message, Snackbar.LENGTH_LONG) - .also { snackbar -> - snackbar.setBackgroundTint(layout.getColor(R.color.warning_warning)) - snackbar.show() - } - } - } - - fun showNotification(message: Int, args: List? = null) { - baseLayout.get()?.let { - showNotification(getMessageString(it.context, message, args)) - } - } - - fun showToastNotification(message: Int, args: List? = null) { - baseLayout.get()?.let { - Toast.makeText(it.context, getMessageString(it.context, message, args), Toast.LENGTH_LONG).show() - } - } - - fun showNotification(errorList: List?>>, builder: (List) -> String) { - val context = baseLayout.get()?.context ?: return - - val message = builder(errorList.map { getMessageString(context, it.first, it.second) }) - showNotification(message) - } - - fun showDebugErrorNotification(message: Int, args: List? = null) { - baseLayout.get()?.let { - showDebugNotification(getMessageString(it.context, message, args)) - } - } -} - -fun getMessageString(context: Context, message: Int, args: List?): String { - return if (args.isNullOrEmpty()) { - context.getString(message) - } else { - context.getString(message, *args.toTypedArray()) - } -} - -val notificationsMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - handleNotificationAction(action) - next(action) - } - } -} - -@Suppress("ComplexMethod") -private fun handleNotificationAction(action: Action) { - if (action is Debug && !BuildConfig.DEBUG) return - - when (action) { - is NotificationAction -> { - notificationsHandler?.showNotification(action.messageResource) - } - is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource) - is ErrorAction -> { - when (action) { - is Debug -> { - val args = (action.error as? ArgError)?.args ?: listOf() - when (action) { - is DebugNotification -> { - notificationsHandler?.showNotification(action.error.messageResource, args) - } - is DebugToastNotification -> { - notificationsHandler?.showToastNotification(action.error.messageResource, args) - } - is DebugErrorAction -> { - notificationsHandler?.showDebugErrorNotification(action.error.messageResource, args) - } - } - } - else -> { - when (action.error) { - is MultiMessageError -> { - val multiError = action.error as MultiMessageError - notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder) - } - else -> { - val args = (action.error as? ArgError)?.args ?: listOf() - notificationsHandler?.showNotification(action.error.messageResource, args) - } - } - } - } - } - } -} - -interface ToastNotificationAction : Action { - val messageResource: Int -} - -interface NotificationAction : Action { - val messageResource: Int -} - -interface ErrorAction : Action { - val error: TapError -} - -// Processed only in the debug builds -interface Debug -interface DebugNotification : Debug, NotificationAction -interface DebugToastNotification : Debug, ToastNotificationAction -interface DebugErrorAction : Debug, ErrorAction \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 79947dc0ac..0a43a25887 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -5,17 +5,10 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.redux.StateDialog -import com.tangem.tap.common.redux.DebugErrorAction -import com.tangem.tap.common.redux.ErrorAction -import com.tangem.tap.domain.TapError import org.rekotlin.Action sealed class GlobalAction : Action { - // notifications - data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction - data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction - // dialogs data class ShowDialog(val stateDialog: StateDialog) : GlobalAction() object HideDialog : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt index bcebe2fe83..19f2b9b8a7 100644 --- a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt +++ b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt @@ -42,7 +42,11 @@ internal class ProxyAppRouter( override fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) { safeNavigate(onComplete, message = "Replace all routes with $routes") { - innerRouter.replaceAll(*routes, onComplete = onComplete) + runCatching { + innerRouter.replaceAll(*routes, onComplete = onComplete) + }.getOrElse { + Timber.tag("ASDASD").e(it) + } } } @@ -78,6 +82,7 @@ internal class ProxyAppRouter( override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) { if (!isSuccess) { + Timber.tag("ASDASD").d(errorMessage) analyticsExceptionHandler.sendException(ExceptionAnalyticsEvent(RuntimeException(errorMessage))) Timber.w(errorMessage) diff --git a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt index 9709e18553..6cd367b582 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt @@ -3,22 +3,13 @@ package com.tangem.tap.routing.component import android.content.Intent import androidx.compose.runtime.Immutable 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.core.ui.decompose.ComposableContentComponent import com.tangem.utils.Provider internal interface RoutingComponent : ComposableContentComponent { - // TODO: Remove after full navigation refactoring - val router: Router - - // TODO: Remove after full navigation refactoring - val stack: Value> - @Immutable sealed class Child { diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 759951e25e..fe42e73ef0 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -1,14 +1,11 @@ package com.tangem.tap.routing.component.impl import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -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.decompose.value.subscribe -import com.arkivanov.essenty.lifecycle.doOnDestroy import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext @@ -23,7 +20,6 @@ import com.tangem.tap.routing.RootContent import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent.Child import com.tangem.tap.routing.configurator.AppRouterConfig -import com.tangem.tap.routing.toggle.RoutingFeatureToggles import com.tangem.tap.routing.utils.ChildFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -36,35 +32,30 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val childFactory: ChildFactory, private val appRouterConfig: AppRouterConfig, private val uiDependencies: UiDependencies, - routingFeatureToggles: RoutingFeatureToggles, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { - override val stack: Value> = childStack( + private val stack: Value> = childStack( source = navigationProvider.getOrCreateTyped(), initialStack = { getInitialStackOrInit() }, serializer = null, // AppRoute.serializer(), // Disabled until Nav refactoring completes - handleBackButton = routingFeatureToggles.isNavigationRefactoringEnabled, - childFactory = ::child, + handleBackButton = true, + childFactory = { route, childContext -> + childFactory.createChild(route, childByContext(childContext)) + }, ) init { - lifecycle.doOnDestroy { - childFactory.doOnDestroy() - } + appRouterConfig.routerScope = componentScope + appRouterConfig.componentRouter = router + appRouterConfig.snackbarHandler = this - if (routingFeatureToggles.isNavigationRefactoringEnabled) { - appRouterConfig.routerScope = componentScope - appRouterConfig.componentRouter = router - appRouterConfig.snackbarHandler = this + stack.subscribe(lifecycle) { stack -> + val stackItems = stack.items.map { it.configuration } - stack.subscribe(lifecycle) { stack -> - val stackItems = stack.items.map { it.configuration } - - if (appRouterConfig.stack != stackItems) { - appRouterConfig.stack = stackItems - } + if (appRouterConfig.stack != stackItems) { + appRouterConfig.stack = stackItems } } } @@ -114,10 +105,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor( initialStack } - private fun child(route: AppRoute, context: ComponentContext): Child { - return childFactory.createChild(route) { childByContext(context) } - } - @AssistedFactory interface Factory : RoutingComponent.Factory { override fun create(context: AppComponentContext, initialStack: List?): DefaultRoutingComponent diff --git a/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt b/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt deleted file mode 100644 index 6554684732..0000000000 --- a/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.tap.routing.toggle - -import com.tangem.core.configtoggle.feature.FeatureTogglesManager -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -internal class RoutingFeatureToggles @Inject constructor( - private val featureTogglesManager: FeatureTogglesManager, -) { - - val isNavigationRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "NAVIGATION_REFACTORING") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f5feb80a77..0f2b7d5303 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -35,10 +35,7 @@ import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectCompone import com.tangem.tap.features.home.api.HomeComponent import com.tangem.tap.features.welcome.component.WelcomeComponent import com.tangem.tap.routing.component.RoutingComponent.Child -import com.tangem.tap.routing.toggle.RoutingFeatureToggles -import com.tangem.utils.Provider import dagger.hilt.android.scopes.ActivityScoped -import java.util.WeakHashMap import javax.inject.Inject import com.tangem.features.walletconnect.components.WalletConnectEntryComponent as RedesignedWalletConnectComponent @@ -80,20 +77,11 @@ internal class ChildFactory @Inject constructor( private val nftComponentFactory: NFTComponent.Factory, private val nftSendComponentFactory: NFTSendComponent.Factory, private val testerRouter: TesterRouter, - private val routingFeatureToggles: RoutingFeatureToggles, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) { - fun createChild(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child { - return if (routingFeatureToggles.isNavigationRefactoringEnabled) { - createChildNew(route, contextFactory(route)) - } else { - createChildLegacy(route, contextFactory) - } - } - @Suppress("LongMethod", "CyclomaticComplexMethod") - private fun createChildNew(route: AppRoute, context: AppComponentContext): Child { + fun createChild(route: AppRoute, context: AppComponentContext): Child { return when (route) { is AppRoute.Initial -> { Child.Initial @@ -419,355 +407,5 @@ internal class ChildFactory @Inject constructor( ) } } - // endregion - } - - @Suppress("LongMethod", "CyclomaticComplexMethod") - private fun createChildLegacy(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child { - componentContexts[route] = contextFactory(route) - - // region Child creation - return when (route) { - is AppRoute.Initial -> { - Child.Initial - } - is AppRoute.AccessCodeRecovery -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = accessCodeRecoveryComponentFactory, - ) - } - is AppRoute.AppCurrencySelector -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = appCurrencySelectorComponentFactory, - ) - } - is AppRoute.Send -> { - if (sendFeatureToggles.isSendV2Enabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = com.tangem.features.send.v2.api.SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactoryV2, - ) - } else { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactory, - ) - } - } - is AppRoute.AppSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = appSettingsComponentFactory, - ) - } - is AppRoute.CardSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = CardSettingsComponent.Params(userWalletId = route.userWalletId), - componentFactory = cardSettingsComponentFactory, - ) - } - is AppRoute.Details -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DetailsComponent.Params(route.userWalletId), - componentFactory = detailsComponentFactory, - ) - } - is AppRoute.DetailsSecurity -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SecurityModeComponent.Params( - userWalletId = route.userWalletId, - ), - componentFactory = securityModeComponentFactory, - ) - } - is AppRoute.Disclaimer -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DisclaimerComponent.Params(route.isTosAccepted), - componentFactory = disclaimerComponentFactory, - ) - } - is AppRoute.Home -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = homeComponentFactory, - ) - } - is AppRoute.ManageTokens -> { - val source = when (route.source) { - AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS - AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING - AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES - } - - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ManageTokensComponent.Params(route.userWalletId, source), - componentFactory = manageTokensComponentFactory, - ) - } - is AppRoute.QrScanning -> { - val source = when (route.source) { - is AppRoute.QrScanning.Source.Send -> SourceType.SEND - is AppRoute.QrScanning.Source.WalletConnect -> SourceType.WALLET_CONNECT - } - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = QrScanningComponent.Params( - source = source, - networkName = (route.source as? AppRoute.QrScanning.Source.Send)?.networkName, - ), - componentFactory = qrScanningComponentFactory, - ) - } - is AppRoute.ReferralProgram -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ReferralComponent.Params(route.userWalletId), - componentFactory = referralComponentFactory, - ) - } - is AppRoute.ResetToFactory -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ResetCardComponent.Params( - userWalletId = route.userWalletId, - cardId = route.cardId, - isActiveBackupStatus = route.isActiveBackupStatus, - backupCardsCount = route.backupCardsCount, - ), - componentFactory = resetCardComponentFactory, - ) - } - is AppRoute.Swap -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SwapComponent.Params( - currencyFrom = route.currencyFrom, - currencyTo = route.currencyTo, - userWalletId = route.userWalletId, - isInitialReverseOrder = route.isInitialReverseOrder, - screenSource = route.screenSource, - ), - componentFactory = swapComponentFactory, - ) - } - is AppRoute.Wallet -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = walletComponentFactory, - ) - } - is AppRoute.WalletConnectSessions -> { - if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = redesignedWalletConnectComponentFactory, - ) - } else { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = walletConnectComponentFactory, - ) - } - } - is AppRoute.CurrencyDetails -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = TokenDetailsComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - ), - componentFactory = tokenDetailsComponentFactory, - ) - } - is AppRoute.Welcome -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = WelcomeComponent.Params( - intent = route.intent, - ), - componentFactory = welcomeComponentFactory, - ) - } - is AppRoute.TesterMenu -> { - Child.LegacyIntent(testerRouter.getEntryIntent()) - } - is AppRoute.Staking -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = StakingComponent.Params( - userWalletId = route.userWalletId, - cryptoCurrencyId = route.cryptoCurrencyId, - yieldId = route.yieldId, - ), - componentFactory = stakingComponentFactory, - ) - } - is AppRoute.PushNotification -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = pushNotificationsComponentFactory, - ) - } - is AppRoute.WalletSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = WalletSettingsComponent.Params(route.userWalletId), - componentFactory = walletSettingsComponentFactory, - ) - } - is AppRoute.MarketsTokenDetails -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = MarketsTokenDetailsComponent.Params( - token = route.token, - appCurrency = route.appCurrency, - showPortfolio = route.showPortfolio, - analyticsParams = route.analyticsParams?.let { - MarketsTokenDetailsComponent.AnalyticsParams( - blockchain = it.blockchain, - source = it.source, - ) - }, - ), - componentFactory = marketsTokenDetailsComponentFactory, - ) - } - is AppRoute.Onramp -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnrampComponent.Params( - userWalletId = route.userWalletId, - cryptoCurrency = route.currency, - source = route.source, - ), - componentFactory = onrampComponentFactory, - ) - } - is AppRoute.OnrampSuccess -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnrampSuccessComponent.Params(route.txId), - componentFactory = onrampSuccessComponentFactory, - ) - } - is AppRoute.BuyCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = BuyCryptoComponent.Params(userWalletId = route.userWalletId), - componentFactory = buyCryptoComponentFactory, - ) - } - is AppRoute.SellCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SellCryptoComponent.Params(userWalletId = route.userWalletId), - componentFactory = sellCryptoComponentFactory, - ) - } - is AppRoute.SwapCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId), - componentFactory = swapSelectTokensComponentFactory, - ) - } - is AppRoute.Onboarding -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnboardingEntryComponent.Params( - scanResponse = route.scanResponse, - mode = when (route.mode) { - AppRoute.Onboarding.Mode.Onboarding -> OnboardingEntryComponent.Mode.Onboarding - AppRoute.Onboarding.Mode.AddBackupWallet1 -> OnboardingEntryComponent.Mode.AddBackupWallet1 - AppRoute.Onboarding.Mode.WelcomeOnlyTwin -> OnboardingEntryComponent.Mode.WelcomeOnlyTwin - AppRoute.Onboarding.Mode.RecreateWalletTwin -> - OnboardingEntryComponent.Mode.RecreateWalletTwin - AppRoute.Onboarding.Mode.ContinueFinalize -> - OnboardingEntryComponent.Mode.ContinueFinalize - }, - ), - componentFactory = onboardingEntryComponentFactory, - ) - } - is AppRoute.Stories -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = StoriesComponent.Params( - storyId = route.storyId, - nextScreen = route.nextScreen, - screenSource = route.screenSource, - ), - componentFactory = storiesComponentFactory, - ) - } - is AppRoute.NFT -> - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = NFTComponent.Params( - userWalletId = route.userWalletId, - walletName = route.walletName, - ), - componentFactory = nftComponentFactory, - ) - is AppRoute.NFTSend -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = NFTSendComponent.Params( - userWalletId = route.userWalletId, - nftAsset = route.nftAsset, - nftCollectionName = route.nftCollectionName, - ), - componentFactory = nftSendComponentFactory, - ) - } - } - // endregion - } - - fun doOnDestroy() { - componentContexts.clear() - } - - private fun contextProvider( - appRoute: AppRoute, - contextFactory: (route: AppRoute) -> AppComponentContext, - ): Provider = Provider { - componentContexts.getOrPut(appRoute) { contextFactory(appRoute) } - } - - private companion object { - - val componentContexts = WeakHashMap() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt index f20d2b096b..6a9fac9177 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt @@ -1,43 +1,9 @@ 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.decompose.ComposableContentComponent -import com.tangem.tap.DecomposeFragment import com.tangem.tap.routing.component.RoutingComponent.Child -import com.tangem.utils.Provider - -internal fun AppRoute.asFragmentChild(fragmentProvider: Provider): Child { - val provider = Provider { - val bundle = (this as? RouteBundleParams)?.getBundle() - - fragmentProvider().apply { - arguments = bundle - } - } - - return Child.LegacyFragment(path, provider) -} - -internal fun > AppRoute.asComponentChild( - contextProvider: Provider, - params: P, - componentFactory: F, -): Child { - val fragmentProvider = Provider { - DecomposeFragment.newInstance( - tag = path, - contextProvider = contextProvider, - params = params, - componentFactory = componentFactory, - ) - } - - return Child.LegacyFragment(path, fragmentProvider) -} internal fun > createComponentChild( context: AppComponentContext, diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index dac7964d66..0000000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 5077c92b05..239af3a94b 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -7,10 +7,6 @@ "name": "ONRAMP_ENABLED", "version": "undefined" }, - { - "name": "NAVIGATION_REFACTORING", - "version": "5.23.0" - }, { "name": "VISA_ONBOARDING_ENABLED", "version": "undefined"