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 8a850fef..873c4afa 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt @@ -1,6 +1,5 @@ package com.rtbishop.look4sat.presentation -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar @@ -10,9 +9,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -20,16 +18,14 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.rtbishop.look4sat.R -import com.rtbishop.look4sat.presentation.info.InfoScreen -import com.rtbishop.look4sat.presentation.map.MapScreen -import com.rtbishop.look4sat.presentation.passes.PassesScreen -import com.rtbishop.look4sat.presentation.passes.PassesViewModel -import com.rtbishop.look4sat.presentation.radar.RadarScreen -import com.rtbishop.look4sat.presentation.satellites.SatellitesScreen -import com.rtbishop.look4sat.presentation.satellites.SatellitesViewModel -import com.rtbishop.look4sat.presentation.settings.SettingsScreen +import com.rtbishop.look4sat.presentation.info.infoDestination +import com.rtbishop.look4sat.presentation.map.mapDestination +import com.rtbishop.look4sat.presentation.passes.passesDestination +import com.rtbishop.look4sat.presentation.radar.radarDestination +import com.rtbishop.look4sat.presentation.satellites.satellitesDestination +import com.rtbishop.look4sat.presentation.settings.settingsDestination -private sealed class Screen(var title: String, var icon: Int, var route: String) { +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") @@ -42,46 +38,40 @@ private sealed class Screen(var title: String, var icon: Int, var route: String) @Composable fun MainScreen() { val outerNavController: NavHostController = rememberNavController() - val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}" - val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 }, - navArgument("aosTime") { defaultValue = 0L }) - val navToRadar = { catNum: Int, aosTime: Long -> - val navRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}" - outerNavController.navigate(navRoute) + val navigateToRadar = { catNum: Int, aosTime: Long -> + val routeWithParams = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}" + outerNavController.navigate(routeWithParams) } + val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}" + val radarArgs = listOf( + navArgument("catNum") { defaultValue = 0 }, + navArgument("aosTime") { defaultValue = 0L } + ) NavHost(navController = outerNavController, startDestination = Screen.Main.route) { - composable(Screen.Main.route) { NavBarScreen(navToRadar) } - composable(radarRoute, radarArgs) { RadarScreen() } + mainDestination(navigateToRadar) + radarDestination(radarRoute, radarArgs) } } +private fun NavGraphBuilder.mainDestination(navigateToRadar: (Int, Long) -> Unit) { + composable(Screen.Main.route) { NavBarScreen(navigateToRadar) } +} + @Composable -private fun NavBarScreen(navToRadar: (Int, Long) -> Unit) { +private fun NavBarScreen(navigateToRadar: (Int, Long) -> Unit) { val innerNavController: NavHostController = rememberNavController() - val navToPasses = { innerNavController.navigate(Screen.Passes.route) } - Scaffold(bottomBar = { MainNavBar(navController = innerNavController) }) { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { - NavHost(navController = innerNavController, startDestination = Screen.Passes.route) { - composable(Screen.Satellites.route) { - val viewModel = viewModel( - modelClass = SatellitesViewModel::class.java, - factory = SatellitesViewModel.Factory - ) - val uiState = viewModel.uiState.collectAsStateWithLifecycle().value - SatellitesScreen(uiState, navToPasses) - } - composable(Screen.Passes.route) { - val viewModel = viewModel( - modelClass = PassesViewModel::class.java, - factory = PassesViewModel.Factory - ) - val uiState = viewModel.uiState.value - PassesScreen(uiState, navToRadar) - } - composable(Screen.Map.route) { MapScreen() } - composable(Screen.Settings.route) { SettingsScreen() } - composable(Screen.Info.route) { InfoScreen() } - } + 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() } } } @@ -89,14 +79,13 @@ private fun NavBarScreen(navToRadar: (Int, Long) -> Unit) { @Composable private fun MainNavBar(navController: NavController) { val items = listOf(Screen.Satellites, Screen.Passes, Screen.Map, Screen.Settings, Screen.Info) - val currentBackStackEntry = navController.currentBackStackEntryAsState() - val currentRoute = currentBackStackEntry.value?.destination?.route + val destinationRoute = navController.currentBackStackEntryAsState().value?.destination?.route NavigationBar { items.forEach { item -> NavigationBarItem( icon = { Icon(painterResource(item.icon), item.title) }, label = { Text(item.title) }, - selected = currentRoute?.contains(item.route) ?: false, + selected = destinationRoute?.contains(item.route) ?: false, onClick = { navController.navigate(item.route) { popUpTo(navController.graph.startDestinationId) { saveState = 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 23c64830..0b9d9ed1 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 @@ -21,16 +21,23 @@ import androidx.compose.ui.text.style.TextAlign 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.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.presentation.MainTheme +import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardButton 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() } +} + @Composable -fun InfoScreen() { +private fun InfoScreen() { LazyColumn(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { item { CardCredits() } } 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 abf61606..1504f06f 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 @@ -27,10 +27,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavGraphBuilder +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.Screen import org.osmdroid.tileprovider.tilesource.XYTileSource import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController @@ -61,8 +64,12 @@ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { } private val labelRect = Rect() +fun NavGraphBuilder.mapDestination() { + composable(Screen.Map.route) { MapScreen() } +} + @Composable -fun MapScreen() { +private fun MapScreen() { val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory) viewModel.selectDefaultSatellite(-1) val stationPos = viewModel.stationPosition.collectAsState(initial = null) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt index cc9bbee7..960c4d70 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesDialog.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -32,16 +31,13 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Checkbox -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf @@ -55,6 +51,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog import com.rtbishop.look4sat.R import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.components.CardButton @@ -73,80 +70,79 @@ private fun PassesDialogPreview() { MainTheme { PassesDialog(24, 16.0, {}) { _, _ -> } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PassesDialog(hours: Int, elev: Double, dismiss: () -> Unit, save: (Int, Double) -> Unit) { val maxWidthModifier = Modifier.fillMaxWidth() val hoursValue = remember { mutableIntStateOf(hours) } val elevationValue = remember { mutableIntStateOf(elev.toInt()) } - ModalBottomSheet(onDismissRequest = { dismiss() }) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp), - modifier = maxWidthModifier.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 32.dp) - ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = maxWidthModifier + Dialog(onDismissRequest = { dismiss() }) { + ElevatedCard { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = maxWidthModifier.padding(16.dp) ) { - CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel)) Text(text = "Passes", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary) - CardButton( - onClick = { - save(hoursValue.intValue, elevationValue.intValue.toDouble()) - dismiss() - }, text = stringResource(id = R.string.btn_accept) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { + Text(text = "Show passes above", fontSize = 16.sp, modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(id = R.drawable.ic_elevation), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(20.dp) + .padding(bottom = 4.dp) + ) + Text( + text = "${elevationValue.intValue}°", + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.primary + ) + } + Slider( + value = elevationValue.intValue.toFloat(), + onValueChange = { elevationValue.intValue = it.toInt() }, + valueRange = 0f..60f ) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { + Text(text = "Show passes within", fontSize = 16.sp, modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(id = R.drawable.ic_time), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(20.dp) + .padding(bottom = 4.dp) + ) + Text( + text = "${hoursValue.intValue}h", + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.primary + ) + } + Slider( + value = hoursValue.intValue.toFloat(), + onValueChange = { hoursValue.intValue = it.toInt() }, + valueRange = 1f..240f + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = maxWidthModifier + ) { + CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel)) + CardButton( + onClick = { + save(hoursValue.intValue, elevationValue.intValue.toDouble()) + dismiss() + }, text = stringResource(id = R.string.btn_accept) + ) + } } - Spacer(modifier = Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { - Text(text = "Show passes above", fontSize = 16.sp, modifier = Modifier.weight(1f)) - Icon( - painter = painterResource(id = R.drawable.ic_elevation), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier - .size(20.dp) - .padding(bottom = 4.dp) - ) - Text( - text = "${elevationValue.intValue}°", - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.primary - ) - } - Slider( - value = elevationValue.intValue.toFloat(), - onValueChange = { elevationValue.intValue = it.toInt() }, - valueRange = 0f..60f, - colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant) - ) - Spacer(modifier = Modifier.height(8.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom) { - Text(text = "Show passes within", fontSize = 16.sp, modifier = Modifier.weight(1f)) - Icon( - painter = painterResource(id = R.drawable.ic_time), - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier - .size(20.dp) - .padding(bottom = 4.dp) - ) - Text( - text = "${hoursValue.intValue}h", - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.primary - ) - } - Slider( - value = hoursValue.intValue.toFloat(), - onValueChange = { hoursValue.intValue = it.toInt() }, - valueRange = 1f..240f, - colors = SliderDefaults.colors(inactiveTrackColor = MaterialTheme.colorScheme.onSurfaceVariant) - ) } } } @@ -157,73 +153,69 @@ private fun RadiosDialogPreview() { MainTheme { RadiosDialog(emptyList(), {}) { _ -> } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RadiosDialog(modes: List, dismiss: () -> Unit, save: (List) -> Unit) { val maxWidthModifier = Modifier.fillMaxWidth(1f) val selected = remember { mutableStateListOf().apply { addAll(modes) } } val select = { mode: String -> if (selected.contains(mode)) selected.remove(mode) else selected.add(mode) } - ModalBottomSheet( - onDismissRequest = { dismiss() }, - sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), - modifier = Modifier.fillMaxHeight(0.80f) - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp), - modifier = maxWidthModifier.padding(horizontal = 12.dp) - ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = maxWidthModifier + Dialog(onDismissRequest = { dismiss() }) { + ElevatedCard { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = maxWidthModifier.padding(vertical = 16.dp) ) { - CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel)) Text(text = "Radios", fontSize = 18.sp, color = MaterialTheme.colorScheme.primary) - CardButton( - onClick = { - save(selected.toList()) - dismiss() - }, text = stringResource(id = R.string.btn_accept) - ) - } - LazyVerticalGrid( - columns = GridCells.Fixed(1), - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .weight(1f), - horizontalArrangement = Arrangement.spacedBy(1.dp), - verticalArrangement = Arrangement.spacedBy(1.dp) - ) { - item { HorizontalDivider(thickness = 0.dp, color = MaterialTheme.colorScheme.surface) } - itemsIndexed(allModes) { index, item -> - Surface { - Row(verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .clickable { select(item) }) { - Text( - text = "$index).", - modifier = Modifier.padding(start = 8.dp, end = 6.dp), - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.primary - ) - Text( - text = item, - modifier = Modifier.weight(1f), - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Checkbox( - checked = selected.contains(item), - onCheckedChange = null, - modifier = Modifier.padding(8.dp) - ) + LazyVerticalGrid( + columns = GridCells.Fixed(1), + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .weight(1f), + horizontalArrangement = Arrangement.spacedBy(1.dp), + verticalArrangement = Arrangement.spacedBy(1.dp) + ) { + item { HorizontalDivider(thickness = 0.dp, color = MaterialTheme.colorScheme.surface) } + itemsIndexed(allModes) { index, item -> + Surface { + Row(verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .background(MaterialTheme.colorScheme.surface) + .clickable { select(item) }) { + Text( + text = "$index).", + modifier = Modifier.padding(start = 8.dp, end = 6.dp), + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = item, + modifier = Modifier.weight(1f), + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Checkbox( + checked = selected.contains(item), + onCheckedChange = null, + modifier = Modifier.padding(8.dp) + ) + } } } + item { HorizontalDivider(thickness = 24.dp, color = MaterialTheme.colorScheme.surface) } + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = maxWidthModifier.padding(horizontal = 16.dp) + ) { + CardButton(onClick = { dismiss() }, text = stringResource(id = R.string.btn_cancel)) + CardButton( + onClick = { + save(selected.toList()) + dismiss() + }, text = stringResource(id = R.string.btn_accept) + ) } - item { HorizontalDivider(thickness = 24.dp, color = MaterialTheme.colorScheme.surface) } } } } 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 8c84dbf0..3caed898 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 @@ -33,12 +33,16 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavGraphBuilder +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.MainTheme +import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardIcon import com.rtbishop.look4sat.presentation.components.NextPassRow import com.rtbishop.look4sat.presentation.components.TimerBar @@ -50,8 +54,19 @@ 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) { + composable(Screen.Passes.route) { + val viewModel = viewModel( + modelClass = PassesViewModel::class.java, + factory = PassesViewModel.Factory + ) + val uiState = viewModel.uiState.value + PassesScreen(uiState, navigateToRadar) + } +} + @Composable -fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) { +private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> Unit) { val refreshPasses = { uiState.takeAction(PassesAction.RefreshPasses) } val showPassesDialog = { uiState.takeAction(PassesAction.TogglePassesDialog) } val showRadiosDialog = { uiState.takeAction(PassesAction.ToggleRadiosDialog) } @@ -72,7 +87,7 @@ fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) { CardIcon(onClick = { showRadiosDialog() }, iconId = R.drawable.ic_satellite) } NextPassRow(pass = uiState.nextPass) - PassesList(uiState.isRefreshing, uiState.itemsList, navToRadar, refreshPasses) + PassesList(uiState.isRefreshing, uiState.itemsList, navigateToRadar, refreshPasses) } } @@ -81,7 +96,7 @@ fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) { private fun PassesList( isRefreshing: Boolean, passes: List, - navToRadar: (Int, Long) -> Unit, + navigateToRadar: (Int, Long) -> Unit, refreshPasses: () -> Unit ) { val refreshState = rememberPullToRefreshState() @@ -102,11 +117,19 @@ private fun PassesList( ) { LazyColumn(modifier = Modifier.fillMaxSize()) { items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass -> - PassItem( - pass = pass, - navToRadar = navToRadar, - modifier = Modifier.animateItem() - ) + if (pass.isDeepSpace) { + DeepSpacePass( + pass = pass, + navigateToRadar = navigateToRadar, + modifier = Modifier.animateItem() + ) + } else { + NearEarthPass( + pass = pass, + navigateToRadar = navigateToRadar, + modifier = Modifier.animateItem() + ) + } } } } @@ -121,7 +144,113 @@ private fun DeepSpacePassPreview() { ) val satellite = DeepSpaceObject(data) val pass = OrbitalPass(1L, 180.0, 10L, 360.0, 36650, 45.0, satellite, 0.5f) - MainTheme { PassItem(pass = pass, { _, _ -> }) } + MainTheme { DeepSpacePass(pass = pass, { _, _ -> }) } +} + +@Composable +private fun DeepSpacePass( + pass: OrbitalPass, + navigateToRadar: (Int, Long) -> Unit, + modifier: Modifier = Modifier +) { + val passSatId = stringResource(id = R.string.pass_satId, pass.catNum) + Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) { + Surface(modifier = Modifier + .padding(bottom = 2.dp) + .clickable { navigateToRadar(pass.catNum, pass.aosTime) }) { + Column( + verticalArrangement = Arrangement.spacedBy(1.dp), + modifier = Modifier + .background(color = MaterialTheme.colorScheme.surface) + .padding(horizontal = 6.dp, vertical = 4.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "$passSatId - ", + color = MaterialTheme.colorScheme.primary + ) + Text( + text = pass.name, + modifier = Modifier + .weight(1f) + .padding(end = 6.dp), + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Icon( + painter = painterResource(id = R.drawable.ic_elevation), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${pass.maxElevation}°", + color = MaterialTheme.colorScheme.primary + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_arrow), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "DeepSpace", + fontSize = 15.sp + ) + } + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_altitude), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "${pass.altitude} km", + fontSize = 15.sp + ) + } + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(id = R.drawable.ic_direction), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource( + id = R.string.pass_aosLos, + pass.aosAzimuth.toInt(), + pass.losAzimuth.toInt() + ), + fontSize = 15.sp + ) + } + } + } + } + } } @Preview(showBackground = true) @@ -132,16 +261,20 @@ private fun NearEarthPassPreview() { ) val satellite = NearEarthObject(data) val pass = OrbitalPass(1L, 180.0, 10L, 360.0, 36650, 45.0, satellite, 0.5f) - MainTheme { PassItem(pass = pass, { _, _ -> }) } + MainTheme { NearEarthPass(pass = pass, { _, _ -> }) } } @Composable -private fun PassItem(pass: OrbitalPass, navToRadar: (Int, Long) -> Unit, modifier: Modifier = Modifier) { +private fun NearEarthPass( + pass: OrbitalPass, + navigateToRadar: (Int, Long) -> Unit, + modifier: Modifier = Modifier +) { val passSatId = stringResource(id = R.string.pass_satId, pass.catNum) Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) { Surface(modifier = Modifier .padding(bottom = 2.dp) - .clickable { navToRadar(pass.catNum, pass.aosTime) }) { + .clickable { navigateToRadar(pass.catNum, pass.aosTime) }) { Column( verticalArrangement = Arrangement.spacedBy(1.dp), modifier = Modifier 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 c54941da..47d3c49f 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 @@ -26,11 +26,18 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.SatRadio +fun NavGraphBuilder.radarDestination(radarRoute: String, radarArgs: List) { + composable(radarRoute, radarArgs) { RadarScreen() } +} + @Composable -fun RadarScreen() { +private fun RadarScreen() { val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory) val currentPass = viewModel.getPass().collectAsState(null).value val id = currentPass?.catNum ?: 99999 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 dc03ebd3..557f1bf8 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 @@ -36,14 +36,30 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.SatItem 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 +fun NavGraphBuilder.satellitesDestination(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) + } +} + @Composable -fun SatellitesScreen(uiState: SatellitesState, navToPasses: () -> Unit) { +private fun SatellitesScreen(uiState: SatellitesState, navigateToPasses: () -> Unit) { val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) } if (uiState.isDialogShown) { TypesDialog(items = uiState.typesList, selected = uiState.currentType, toggleDialog) { @@ -57,7 +73,7 @@ fun SatellitesScreen(uiState: SatellitesState, navToPasses: () -> Unit) { uiState.takeAction(SatellitesAction.SearchFor(newQuery)) }, saveSelection = { uiState.takeAction(SatellitesAction.SaveSelection) - navToPasses() + navigateToPasses() }) MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() }) ElevatedCard(modifier = Modifier.fillMaxSize()) { 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 689f9389..bba64fb6 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 @@ -33,10 +33,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavGraphBuilder +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.MainTheme +import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.components.CardButton import com.rtbishop.look4sat.presentation.components.gotoUrl import com.rtbishop.look4sat.presentation.components.showToast @@ -48,8 +51,12 @@ private const val GITHUB_URL = "https://github.com/rt-bishop/Look4Sat/" private const val DONATE_URL = "https://ko-fi.com/rt_bishop" private const val FDROID_URL = "https://f-droid.org/en/packages/com.rtbishop.look4sat/" +fun NavGraphBuilder.settingsDestination() { + composable(Screen.Settings.route) { SettingsScreen() } +} + @Composable -fun SettingsScreen() { +private fun SettingsScreen() { val viewModel = viewModel(SettingsViewModel::class.java, factory = SettingsViewModel.Factory) val context = LocalContext.current @@ -446,16 +453,11 @@ private fun setUpdateTime(updateTime: Long): String { @Composable private fun UpdateIndicator(isUpdating: Boolean, modifier: Modifier = Modifier) = if (isUpdating) { - LinearProgressIndicator( - modifier = modifier.padding(start = 6.dp), -// color = MaterialTheme.colorScheme.primary, -// trackColor = MaterialTheme.colorScheme.secondaryContainer - ) + LinearProgressIndicator(modifier = modifier.padding(start = 6.dp)) } else { LinearProgressIndicator( progress = { 0f }, - modifier = modifier.padding(start = 6.dp), -// color = MaterialTheme.colorScheme.primary, -// trackColor = MaterialTheme.colorScheme.secondaryContainer + drawStopIndicator = {}, + modifier = modifier.padding(start = 6.dp) ) }