From ec1a55c93dbbecb61767b2b43da94a37fa59aa06 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sun, 31 May 2026 21:58:54 +0100 Subject: [PATCH] Added initial code for possible future deeplink nav support --- app/src/main/AndroidManifest.xml | 8 +++ .../java/com/rtbishop/look4sat/MainScreen.kt | 52 +++++++++++++++++-- .../look4sat/core/presentation/Navigation.kt | 30 +++++++++++ .../core/presentation/DeeplinkResolverTest.kt | 39 ++++++++++++++ 4 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 core/presentation/src/test/java/com/rtbishop/look4sat/core/presentation/DeeplinkResolverTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5d6b6ccc..e8a8cf54 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,14 @@ + + + + + + + + Unit = { rootBackStack.removeLastOrNull() } + val slideInTransition = slideInHorizontally(initialOffsetX = { it }) togetherWith scaleOut(targetScale = 0.9f) + val slideOutTransition = scaleIn(initialScale = 0.9f) togetherWith slideOutHorizontally(targetOffsetX = { it }) + NavDisplay( + modifier = Modifier.fillMaxSize(), + backStack = rootBackStack, + onBack = navigateBack, + transitionSpec = { slideInTransition }, + popTransitionSpec = { slideOutTransition }, + predictivePopTransitionSpec = { slideOutTransition }, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), // Required for saving Compose state per entry + rememberViewModelStoreNavEntryDecorator() // Required for ViewModel scoping per entry + ), + entryProvider = entryProvider { + entry { MainScreen(navigateToRadar = { rootBackStack.add(RadarDestination) }) } + entry { + Scaffold { innerPadding -> + RadarDestination(navigateUp = navigateBack) + innerPadding.calculateTopPadding() + } + } + } + ) +} + +@Composable +fun MainScreen(navigateToRadar: () -> Unit = {}) { val backStack = rememberNavBackStack(Screen.Passes) val currentKey = backStack.lastOrNull() - val navigateBack: () -> Unit = { if (backStack.size > 1) backStack.removeAt(backStack.size - 1) } + val navigateBack: () -> Unit = { backStack.removeLastOrNull() } val fadeTransition = fadeIn(animationSpec = tween(350)) togetherWith fadeOut(animationSpec = tween(350)) -// val slideInTransition = slideInHorizontally(initialOffsetX = { it }) togetherWith scaleOut(targetScale = 0.9f) -// val slideOutTransition = scaleIn(initialScale = 0.9f) togetherWith slideOutHorizontally(targetOffsetX = { it }) val navItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Map, Screen.Settings) val context = LocalContext.current @@ -139,6 +182,7 @@ fun MainScreen() { PassesDestination { catNum, aosTime -> container.satelliteRepo.selectPass(catNum, aosTime) backStack.add(Screen.Radar) +// navigateToRadar() } } entry { diff --git a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt index bf4349d8..b0ff0c1b 100644 --- a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt +++ b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Navigation.kt @@ -38,3 +38,33 @@ sealed class Screen(val iconResId: Int, val titleResId: Int) : NavKey { @Serializable data object Settings : Screen(R.drawable.ic_settings, R.string.nav_prefs) } + +@Serializable +data object RadarDestination : NavKey + +interface IDeeplinkMatcher { + fun match(deeplink: String): NavKey? +} + +object PassDetailsMatcher : IDeeplinkMatcher { + val passDetailsRegex = """https://github.com/rt-bishop/Look4Sat/passes/(.*)""".toRegex() + + override fun match(deeplink: String): NavKey? { + val passMatch = passDetailsRegex.find(deeplink) + passMatch?.let { match -> + val passId = match.groupValues[1] + if (passId.isNotEmpty()) return RadarDestination + } + return null + } +} + +class DeeplinkResolver(private val fallbackDestination: NavKey = Screen.Passes) { + + private val matchers: List = listOf(PassDetailsMatcher) + + fun resolve(deeplink: String): NavKey { + matchers.forEach { it.match(deeplink)?.let { match -> return match } } + return fallbackDestination + } +} diff --git a/core/presentation/src/test/java/com/rtbishop/look4sat/core/presentation/DeeplinkResolverTest.kt b/core/presentation/src/test/java/com/rtbishop/look4sat/core/presentation/DeeplinkResolverTest.kt new file mode 100644 index 00000000..9c2738ae --- /dev/null +++ b/core/presentation/src/test/java/com/rtbishop/look4sat/core/presentation/DeeplinkResolverTest.kt @@ -0,0 +1,39 @@ +/* + * Look4Sat. Amateur radio satellite tracker and pass predictor. + * Copyright (C) 2019-2026 Arty Bishop and contributors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.rtbishop.look4sat.core.presentation + +import org.junit.Test + +class DeeplinkResolverTest { + + @Test + fun returnsDefaultDestination() { + val deeplinkResolver = DeeplinkResolver() + val result = deeplinkResolver.resolve("/deeplink") + assert(result == Screen.Passes) + } + + @Test + fun returnsRadarDestination() { + val passId = "some-pass-id" + val passDeeplink = "https://github.com/rt-bishop/Look4Sat/passes/$passId" + val deeplinkResolver = DeeplinkResolver() + val result = deeplinkResolver.resolve(passDeeplink) + assert(result == RadarDestination) + } +}