From 49d793a76393eed70d774eaac7e8005e808c016f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Aug 2024 13:02:27 +0400 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../decompose/navigation/DefaultRouter.kt | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt index 86c43706a6..2834f7ab66 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/navigation/DefaultRouter.kt @@ -3,7 +3,6 @@ package com.tangem.core.decompose.navigation import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop -import com.arkivanov.decompose.router.stack.popWhile import com.arkivanov.decompose.router.stack.pushNew import com.arkivanov.essenty.instancekeeper.InstanceKeeper import kotlin.reflect.KClass @@ -34,16 +33,27 @@ internal class DefaultRouter( } override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) { - navigation.popWhile( - predicate = { it != route }, + popTo( + predicate = { it == route }, onComplete = onComplete, ) } override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) { - navigation.popWhile( - predicate = { it::class != routeClass }, + popTo( + predicate = { routeClass.isInstance(it) }, onComplete = onComplete, ) } + + private fun popTo(predicate: (Route) -> Boolean, onComplete: (isSuccess: Boolean) -> Unit) { + navigation.navigate( + transformer = { stack -> + stack + .dropLastWhile(predicate) + .ifEmpty { stack } + }, + onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) }, + ) + } } \ No newline at end of file From c5362c18899424d9c05a127b391cb916424df85c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 20 Aug 2024 13:04:58 +0400 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 1 + .../com/tangem/tap/routing/ProxyAppRouter.kt | 44 ++++++++++++++----- .../routing/configurator/AppRouterConfig.kt | 4 ++ .../configurator/MutableAppRouterConfig.kt | 2 + .../com/tangem/common/routing/AppRouter.kt | 36 ++++++++++++--- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 8e0a528fca..b8830c622b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -242,6 +242,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac appRouterConfig.routerScope = lifecycleScope appRouterConfig.componentRouter = routingComponent.router + appRouterConfig.snackbarHandler = this routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack -> appRouterConfig.stack = childStack.backStack 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 592656008e..d744032000 100644 --- a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt +++ b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt @@ -1,10 +1,12 @@ package com.tangem.tap.routing +import com.google.firebase.crashlytics.FirebaseCrashlytics 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 com.tangem.wallet.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import timber.log.Timber @@ -31,37 +33,59 @@ internal class ProxyAppRouter( } override fun push(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) { - routerScope.launch(dispatchers.mainImmediate) { - Timber.i("Push route: $route") + safeNavigate(onComplete, message = "Push $route") { innerRouter.push(route, onComplete) } } override fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) { - routerScope.launch(dispatchers.mainImmediate) { - Timber.i("Replace all routes with $routes") + safeNavigate(onComplete, message = "Replace all routes with $routes") { innerRouter.replaceAll(*routes, onComplete = onComplete) } } override fun pop(onComplete: (isSuccess: Boolean) -> Unit) { - routerScope.launch(dispatchers.mainImmediate) { - Timber.i("Pop route") + safeNavigate(onComplete, message = "Pop route") { innerRouter.pop(onComplete) } } override fun popTo(route: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) { - routerScope.launch(dispatchers.mainImmediate) { - Timber.i("Pop to route: $route") + safeNavigate(onComplete, message = "Pop to $route") { innerRouter.popTo(route, onComplete) } } override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) { - routerScope.launch(dispatchers.mainImmediate) { - Timber.i("Pop to route class: $routeClass") + safeNavigate(onComplete, message = "Pop to $routeClass") { innerRouter.popTo(routeClass, onComplete) } } + + private fun safeNavigate(onComplete: (isSuccess: Boolean) -> Unit, message: String, block: () -> Unit) { + routerScope.launch(dispatchers.mainImmediate) { + Timber.i(message) + + try { + block() + } catch (e: Throwable) { + onComplete(false) + } + } + } + + override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) { + if (!isSuccess) { + FirebaseCrashlytics.getInstance().recordException(RuntimeException(errorMessage)) + Timber.w(errorMessage) + + with(receiver = config.snackbarHandler ?: return) { + showSnackbar( + text = R.string.common_unknown_error, + buttonTitle = R.string.common_ok, + action = { dismissSnackbar() }, + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt index 470c85f674..5c2c9b3843 100644 --- a/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt +++ b/app/src/main/java/com/tangem/tap/routing/configurator/AppRouterConfig.kt @@ -2,6 +2,7 @@ package com.tangem.tap.routing.configurator import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.navigation.Router +import com.tangem.tap.common.SnackbarHandler import kotlinx.coroutines.CoroutineScope internal interface AppRouterConfig { @@ -9,4 +10,7 @@ internal interface AppRouterConfig { var routerScope: CoroutineScope? var componentRouter: Router? var stack: List? + + // TODO: Replace with UI message handler: [REDACTED_JIRA] + var snackbarHandler: SnackbarHandler? } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt index ae724ac04e..d1b1e93f7c 100644 --- a/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt +++ b/app/src/main/java/com/tangem/tap/routing/configurator/MutableAppRouterConfig.kt @@ -2,6 +2,7 @@ package com.tangem.tap.routing.configurator import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.navigation.Router +import com.tangem.tap.common.SnackbarHandler import kotlinx.coroutines.CoroutineScope internal class MutableAppRouterConfig : AppRouterConfig { @@ -9,4 +10,5 @@ internal class MutableAppRouterConfig : AppRouterConfig { override var routerScope: CoroutineScope? = null override var componentRouter: Router? = null override var stack: List? = null + override var snackbarHandler: SnackbarHandler? = null } \ No newline at end of file diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt index cb0f21a793..9f8d5f4111 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRouter.kt @@ -23,7 +23,12 @@ interface AppRouter { * @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 = {}) + fun push( + route: AppRoute, + onComplete: (isSuccess: Boolean) -> Unit = { isSuccess -> + defaultCompletionHandler(isSuccess, errorMessage = "Unable to push $route") + }, + ) /** * Replaces ***all*** routes in the navigation stack with the specified [routes]. @@ -31,14 +36,23 @@ interface AppRouter { * @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 = {}) + fun replaceAll( + vararg routes: AppRoute, + onComplete: (isSuccess: Boolean) -> Unit = { isSuccess -> + defaultCompletionHandler(isSuccess, errorMessage = "Unable to replace routes with $routes") + }, + ) /** * 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 = {}) + fun pop( + onComplete: (isSuccess: Boolean) -> Unit = { isSuccess -> + defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop route") + }, + ) /** * Pops routes from the navigation stack until the specified [route] is found. @@ -46,7 +60,12 @@ interface AppRouter { * @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 = {}) + fun popTo( + route: AppRoute, + onComplete: (isSuccess: Boolean) -> Unit = { isSuccess -> + defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop to $route") + }, + ) /** * Pops routes from the navigation stack until the ***first*** specified [routeClass] is found. @@ -54,5 +73,12 @@ interface AppRouter { * @param routeClass The route class to pop to. * @param onComplete The callback to be invoked when the operation is complete. */ - fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit = {}) + fun popTo( + routeClass: KClass, + onComplete: (isSuccess: Boolean) -> Unit = { isSuccess -> + defaultCompletionHandler(isSuccess, errorMessage = "Unable to pop to $routeClass") + }, + ) + + fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) } \ No newline at end of file