diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt index 7fe956a6..24019e01 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt @@ -1,21 +1,28 @@ +@file:OptIn(ExperimentalSharedTransitionApi::class) + package com.rtbishop.look4sat.presentation -import androidx.compose.foundation.layout.padding +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.SharedTransitionScope import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import androidx.navigation.navigation import com.rtbishop.look4sat.R import com.rtbishop.look4sat.presentation.info.infoDestination import com.rtbishop.look4sat.presentation.map.mapDestination @@ -25,69 +32,84 @@ import com.rtbishop.look4sat.presentation.satellites.satellitesDestination import com.rtbishop.look4sat.presentation.settings.settingsDestination sealed class Screen(var title: String, var icon: Int, var route: String) { - data object Main : Screen("Main", R.drawable.ic_sputnik, "main") - data object Radar : Screen("Radar", R.drawable.ic_sputnik, "radar") data object Satellites : Screen("Satellites", R.drawable.ic_sputnik, "satellites") + data object PassesGraph : Screen("Passes", R.drawable.ic_passes, "passesGraph") data object Passes : Screen("Passes", R.drawable.ic_passes, "passes") + data object Radar : Screen("Radar", R.drawable.ic_sputnik, "radar") data object Map : Screen("Map", R.drawable.ic_map, "map") data object Settings : Screen("Settings", R.drawable.ic_settings, "settings") data object Info : Screen("Info", R.drawable.ic_info, "info") } +val LocalSharedTransitionScope = compositionLocalOf { null } +val LocalNavAnimatedVisibilityScope = compositionLocalOf { null } + @Composable fun MainScreen() { - val outerNavController: NavHostController = rememberNavController() + val navController: NavHostController = rememberNavController() + val navigateBack: () -> Unit = { navController.navigateUp() } val navigateToRadar = { catNum: Int, aosTime: Long -> val routeWithParams = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}" - outerNavController.navigate(routeWithParams) + navController.navigate(routeWithParams) } - NavHost(navController = outerNavController, startDestination = Screen.Main.route) { - mainDestination(navigateToRadar) - radarDestination { outerNavController.navigateUp() } - } -} - -private fun NavGraphBuilder.mainDestination(navigateToRadar: (Int, Long) -> Unit) { - composable(Screen.Main.route) { NavBarScreen(navigateToRadar) } -} - -@Composable -private fun NavBarScreen(navigateToRadar: (Int, Long) -> Unit) { - val innerNavController: NavHostController = rememberNavController() - val navigateToPasses = { innerNavController.navigate(Screen.Passes.route) } - Scaffold(bottomBar = { MainNavBar(innerNavController) }) { innerPadding -> - NavHost( - navController = innerNavController, - startDestination = Screen.Passes.route, - modifier = Modifier.padding(innerPadding) - ) { - satellitesDestination(navigateToPasses) - passesDestination(navigateToRadar) - mapDestination() - settingsDestination() - infoDestination() + SharedTransitionLayout { + CompositionLocalProvider(LocalSharedTransitionScope provides this) { + NavHost(navController = navController, startDestination = Screen.PassesGraph.route) { + satellitesDestination(navController, navigateBack) + passesGraph(navController, navigateBack, navigateToRadar) + radarDestination(navigateBack) + mapDestination(navController) + settingsDestination(navController) + infoDestination(navController) + } } } } +private fun NavGraphBuilder.passesGraph( + navController: NavHostController, + navigateBack: () -> Unit, + navigateToRadar: (Int, Long) -> Unit +) { + navigation(route = Screen.PassesGraph.route, startDestination = Screen.Passes.route) { + passesDestination(navController, navigateToRadar) + radarDestination(navigateBack) + } +} + +@SuppressLint("RestrictedApi", "StateFlowValueCalledInComposition") @Composable -private fun MainNavBar(navController: NavController) { +fun MainNavBar(navController: NavController) { + val sharedTransitionScope = LocalSharedTransitionScope.current + ?: throw IllegalStateException("No SharedElementScope found") + val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current + ?: throw IllegalStateException("No SharedElementScope found") + val sharedTransitionState = sharedTransitionScope.rememberSharedContentState("bottomNav") val items = listOf(Screen.Satellites, Screen.Passes, Screen.Map, Screen.Settings, Screen.Info) - val destinationRoute = navController.currentBackStackEntryAsState().value?.destination?.route - NavigationBar { - items.forEach { item -> - NavigationBarItem( - icon = { Icon(painterResource(item.icon), item.title) }, - label = { Text(item.title) }, - selected = destinationRoute?.contains(item.route) ?: false, - onClick = { - navController.navigate(item.route) { - popUpTo(navController.graph.startDestinationId) { saveState = false } - launchSingleTop = true - restoreState = false + val stack = navController.currentBackStack.value.map { it.destination.route } + Log.d("NavBar", "NavStack: $stack") + with(sharedTransitionScope) { + NavigationBar( + modifier = Modifier.sharedElement(sharedTransitionState, animatedVisibilityScope) + ) { + items.forEach { item -> + NavigationBarItem( + icon = { Icon(painterResource(item.icon), item.title) }, + label = { Text(item.title) }, + selected = item.route == navController.currentDestination?.route, + onClick = { + if (item.route == navController.currentDestination?.route) { + // selecting the same tab + } else { + navController.navigate(item.route) { + popUpTo(Screen.Passes.route) { saveState = false } + launchSingleTop = true + restoreState = false + } + } } - } - ) + ) + } } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/info/InfoScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/info/InfoScreen.kt index 0b9d9ed1..2923f37f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/info/InfoScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/info/InfoScreen.kt @@ -1,6 +1,7 @@ package com.rtbishop.look4sat.presentation.info import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,8 +11,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -22,8 +25,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.rtbishop.look4sat.R +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope +import com.rtbishop.look4sat.presentation.MainNavBar import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardButton @@ -32,8 +38,16 @@ import com.rtbishop.look4sat.presentation.components.gotoUrl private const val POLICY_URL = "https://sites.google.com/view/look4sat-privacy-policy/home" private const val LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html" -fun NavGraphBuilder.infoDestination() { - composable(Screen.Info.route) { InfoScreen() } +fun NavGraphBuilder.infoDestination(navController: NavHostController) { + composable(Screen.Info.route) { + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + Scaffold(bottomBar = { MainNavBar(navController) }) { innerPadding -> + Box(Modifier.padding(innerPadding)) { + InfoScreen() + } + } + } + } } @Composable diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt index e2844fc3..33cfc87a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt @@ -15,8 +15,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -37,11 +39,14 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.OrbitalObject import com.rtbishop.look4sat.domain.predict.OrbitalPos +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope +import com.rtbishop.look4sat.presentation.MainNavBar import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardIcon import com.rtbishop.look4sat.presentation.components.NextPassRow @@ -80,12 +85,18 @@ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { } private val labelRect = Rect() -fun NavGraphBuilder.mapDestination() { +fun NavGraphBuilder.mapDestination(navController: NavHostController) { composable(Screen.Map.route) { val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory) val uiState = viewModel.uiState.collectAsStateWithLifecycle() val mapView = rememberMapViewWithLifecycle() - MapScreen(uiState, mapView) + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + Scaffold(bottomBar = { MainNavBar(navController) }) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + MapScreen(uiState, mapView) + } + } + } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index e3936033..94c5f9eb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -3,6 +3,7 @@ package com.rtbishop.look4sat.presentation.passes import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,12 +19,14 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -36,12 +39,15 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.predict.DeepSpaceObject import com.rtbishop.look4sat.domain.predict.NearEarthObject import com.rtbishop.look4sat.domain.predict.OrbitalData import com.rtbishop.look4sat.domain.predict.OrbitalPass +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope +import com.rtbishop.look4sat.presentation.MainNavBar import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardIcon @@ -56,14 +62,20 @@ import java.util.Locale private val sdfDate = SimpleDateFormat("EEE dd MMM", Locale.ENGLISH) private val sdfTime = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH) -fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) { +fun NavGraphBuilder.passesDestination(navController: NavHostController, navigateToRadar: (Int, Long) -> Unit) { composable(Screen.Passes.route) { val viewModel = viewModel( modelClass = PassesViewModel::class.java, factory = PassesViewModel.Factory ) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - PassesScreen(uiState, navigateToRadar) + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + Scaffold(bottomBar = { MainNavBar(navController) }) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + PassesScreen(uiState, navigateToRadar) + } + } + } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt index 66f6efba..a1f2d6ac 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -51,6 +52,7 @@ import androidx.navigation.navArgument import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.utility.toDegrees +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardIcon @@ -68,7 +70,9 @@ fun NavGraphBuilder.radarDestination(navigateBack: () -> Unit) { composable(radarRoute, radarArgs) { val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - RadarScreen(uiState, navigateBack) + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + RadarScreen(uiState, navigateBack) + } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt index e44b635d..da6b5699 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt @@ -22,9 +22,11 @@ import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -41,23 +43,32 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.SatItem +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope +import com.rtbishop.look4sat.presentation.MainNavBar import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardIcon import com.rtbishop.look4sat.presentation.components.CardLoadingIndicator import com.rtbishop.look4sat.presentation.components.InfoDialog -fun NavGraphBuilder.satellitesDestination(navigateToPasses: () -> Unit) { +fun NavGraphBuilder.satellitesDestination(navController: NavHostController, navigateToPasses: () -> Unit) { composable(Screen.Satellites.route) { val viewModel = viewModel( modelClass = SatellitesViewModel::class.java, factory = SatellitesViewModel.Factory ) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - SatellitesScreen(uiState, navigateToPasses) + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + Scaffold(bottomBar = { MainNavBar(navController) }) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + SatellitesScreen(uiState, navigateToPasses) + } + } + } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt index edf5fa31..8160d23e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt @@ -5,6 +5,7 @@ import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,9 +19,11 @@ import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -35,10 +38,13 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.predict.GeoPos +import com.rtbishop.look4sat.presentation.LocalNavAnimatedVisibilityScope +import com.rtbishop.look4sat.presentation.MainNavBar import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardButton @@ -47,14 +53,20 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -fun NavGraphBuilder.settingsDestination() { +fun NavGraphBuilder.settingsDestination(navController: NavHostController) { composable(Screen.Settings.route) { val viewModel = viewModel( modelClass = SettingsViewModel::class.java, factory = SettingsViewModel.Factory ) val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - SettingsScreen(uiState) + CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this@composable) { + Scaffold(bottomBar = { MainNavBar(navController) }) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + SettingsScreen(uiState) + } + } + } } }