Compare commits

..
3 Commits
22 changed files with 243 additions and 212 deletions

No files matched your search

@@ -25,19 +25,17 @@ import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -58,82 +56,107 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation3.runtime.entryProvider
import androidx.navigation.compose.NavHost import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation3.ui.NavDisplay
import androidx.navigation.compose.rememberNavController import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.presentation.Screen import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.hasEnoughHeight import com.rtbishop.look4sat.core.presentation.hasEnoughHeight
import com.rtbishop.look4sat.core.presentation.hasEnoughWidth import com.rtbishop.look4sat.core.presentation.hasEnoughWidth
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider import com.rtbishop.look4sat.feature.map.MapDestination
import com.rtbishop.look4sat.feature.map.mapDestination import com.rtbishop.look4sat.feature.passes.PassesDestination
import com.rtbishop.look4sat.feature.passes.passesDestination import com.rtbishop.look4sat.feature.radar.RadarDestination
import com.rtbishop.look4sat.feature.radar.radarDestination import com.rtbishop.look4sat.feature.radiocontrol.RadioControlDestination
import com.rtbishop.look4sat.feature.radiocontrol.radioControlDestination import com.rtbishop.look4sat.feature.satellites.SatellitesDestination
import com.rtbishop.look4sat.feature.satellites.satellitesDestination import com.rtbishop.look4sat.feature.settings.SettingsDestination
import com.rtbishop.look4sat.feature.settings.settingsDestination
@Composable @Composable
fun MainScreen(navController: NavHostController = rememberNavController()) { fun MainScreen() {
val items = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Map, Screen.Settings) val backStack = rememberNavBackStack(Screen.Passes)
val currentDestination = navController.currentBackStackEntryAsState().value?.destination?.route val currentKey = backStack.lastOrNull()
val startDestination = Screen.Passes.route val navigateBack: () -> Unit = { backStack.removeAt(backStack.size - 1) }
val fadeTransition = fadeIn(animationSpec = tween(350)) togetherWith fadeOut(animationSpec = tween(350))
val navItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar(), Screen.Map, Screen.Settings)
// Observe radio tracking state for the status bar
val context = LocalContext.current val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer() val container = (context.applicationContext as IContainerProvider).getMainContainer()
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle() val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
NavigationSuiteScaffold( NavigationSuiteScaffold(
navigationSuiteItems = { navigationSuiteItems = {
items.forEach { navItems.forEach { screen ->
val isSelected = when (currentKey) {
is Screen.Satellites -> screen is Screen.Satellites
is Screen.Passes -> screen is Screen.Passes
is Screen.Radar -> screen is Screen.Radar
is Screen.Map -> screen is Screen.Map
is Screen.Settings -> screen is Screen.Settings
else -> false
}
item( item(
icon = { Icon(painterResource(it.iconResId), stringResource(it.titleResId)) }, icon = { Icon(painterResource(screen.iconResId), stringResource(screen.titleResId)) },
label = { Text(stringResource(it.titleResId)) }, label = { Text(stringResource(screen.titleResId)) },
selected = currentDestination?.contains(it.route) ?: false, selected = isSelected,
onClick = { onClick = {
if (currentDestination?.contains(it.route) ?: false) return@item if (isSelected) return@item
navController.navigate(it.route) { while (backStack.size > 1) backStack.removeAt(backStack.size - 1)
popUpTo(startDestination) { saveState = false } if (screen !is Screen.Passes) backStack.add(screen)
launchSingleTop = true
restoreState = false
} }
}) )
} }
}, navigationSuiteColors = NavigationSuiteDefaults.colors( },
navigationSuiteColors = NavigationSuiteDefaults.colors(
navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer
), layoutType = when { ),
layoutType = when {
!hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail !hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail
!hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact !hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact
else -> NavigationSuiteType.ShortNavigationBarMedium else -> NavigationSuiteType.ShortNavigationBarMedium
} }
) { ) {
Column { Column {
NavHost( NavDisplay(
navController = navController, backStack = backStack,
startDestination = startDestination, modifier = Modifier.weight(1f),
enterTransition = { fadeIn(animationSpec = tween(350)) }, onBack = navigateBack,
exitTransition = { fadeOut(animationSpec = tween(350)) }, transitionSpec = { fadeTransition },
modifier = Modifier.weight(1f) popTransitionSpec = { fadeTransition },
) { predictivePopTransitionSpec = { fadeTransition },
satellitesDestination { navController.navigateUp() } entryProvider = entryProvider {
passesDestination { catNum: Int, aosTime: Long -> entry<Screen.Satellites> {
val radarRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}" SatellitesDestination(navigateUp = navigateBack)
navController.navigate(radarRoute)
} }
radarDestination( entry<Screen.Passes> {
navigateUp = { navController.navigateUp() }, PassesDestination { catNum, aosTime ->
backStack.add(Screen.Radar(catNum, aosTime))
}
}
entry<Screen.Radar> { route ->
RadarDestination(
catNum = route.catNum,
aosTime = route.aosTime,
navigateUp = navigateBack,
navigateToRadioControl = { catNum, aosTime -> navigateToRadioControl = { catNum, aosTime ->
val route = "${Screen.RadioControl.route}?catNum=$catNum&aosTime=$aosTime" backStack.add(Screen.RadioControl(catNum, aosTime))
navController.navigate(route)
} }
) )
radioControlDestination { navController.navigateUp() }
mapDestination()
settingsDestination()
} }
entry<Screen.RadioControl> { route ->
// Radio tracking status banner (above bottom navigation) RadioControlDestination(
catNum = route.catNum,
aosTime = route.aosTime,
navigateUp = navigateBack
)
}
entry<Screen.Map> {
MapDestination()
}
entry<Screen.Settings> {
SettingsDestination()
}
}
)
// Radio tracking status banner
if (trackingState.isActive) { if (trackingState.isActive) {
val infiniteTransition = rememberInfiniteTransition(label = "trackingPulse") val infiniteTransition = rememberInfiniteTransition(label = "trackingPulse")
val alpha by infiniteTransition.animateFloat( val alpha by infiniteTransition.animateFloat(
@@ -151,8 +174,7 @@ fun MainScreen(navController: NavHostController = rememberNavController()) {
.clickable { .clickable {
val pass = trackingState.currentPass val pass = trackingState.currentPass
if (pass != null) { if (pass != null) {
val route = "${Screen.RadioControl.route}?catNum=${pass.catNum}&aosTime=${pass.aosTime}" backStack.add(Screen.RadioControl(pass.catNum, pass.aosTime))
navController.navigate(route)
} }
} }
.padding(horizontal = 12.dp, vertical = 6.dp) .padding(horizontal = 12.dp, vertical = 6.dp)
@@ -180,6 +202,6 @@ fun MainScreen(navController: NavHostController = rememberNavController()) {
) )
} }
} }
} // end Column }
} }
} }
@@ -38,6 +38,8 @@ internal class ApplicationPlugin : Plugin<Project> {
implementation(project(":feature:satellites")) implementation(project(":feature:satellites"))
implementation(project(":feature:settings")) implementation(project(":feature:settings"))
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation(libs.compose.material3.adaptive)
implementation(libs.compose.navigation3)
androidTestImplementation(libs.bundles.androidTest) androidTestImplementation(libs.bundles.androidTest)
} }
} }
@@ -33,7 +33,7 @@ internal class CoreDataPlugin : Plugin<Project> {
implementation(libs.androidx.room) implementation(libs.androidx.room)
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
ksp(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler)
implementation(libs.other.coroutines) implementation(libs.kotlin.coroutines)
implementation(libs.other.okhttp) implementation(libs.other.okhttp)
} }
} }
@@ -25,10 +25,11 @@ import org.gradle.kotlin.dsl.dependencies
internal class CoreDomainPlugin : Plugin<Project> { internal class CoreDomainPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) { override fun apply(target: Project) = with(target) {
applyPlugin(libs.plugins.kotlin.jvm) applyPlugin(libs.plugins.kotlin.jvm)
applyPlugin(libs.plugins.kotlin.serialization)
setupKotlin() setupKotlin()
dependencies { dependencies {
implementation(libs.other.coroutines) implementation(libs.kotlin.coroutines)
implementation(libs.other.json) implementation(libs.kotlin.serialization)
} }
} }
} }
@@ -24,12 +24,15 @@ import org.gradle.kotlin.dsl.dependencies
@Suppress("Unused") @Suppress("Unused")
internal class CorePresentationPlugin : Plugin<Project> { internal class CorePresentationPlugin : Plugin<Project> {
override fun apply(target: Project) = with(target) { override fun apply(target: Project) = with(target) {
applyPlugin(libs.plugins.kotlin.serialization)
setupAndroidLib() setupAndroidLib()
setupCompose() setupCompose()
setupKotlin() setupKotlin()
dependencies { dependencies {
implementation(project(":core:domain")) implementation(project(":core:domain"))
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation(libs.kotlin.serialization)
implementation(libs.compose.material3.adaptive)
} }
} }
} }
@@ -74,7 +74,7 @@ internal fun Project.setupAndroidApp() {
} }
androidResources { androidResources {
generateLocaleConfig = true generateLocaleConfig = true
localeFilters.addAll(listOf("en", "es", "ru", "si", "uk", "zh")) localeFilters.addAll(listOf("en", "es", "ru", "si", "tr", "uk", "zh"))
} }
packaging { resources { excludes += listOf("META-INF/*") } } packaging { resources { excludes += listOf("META-INF/*") } }
} }
+1
View File
@@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.google.ksp) apply false alias(libs.plugins.google.ksp) apply false
alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
} }
tasks.register("clean", Delete::class.java) { tasks.register("clean", Delete::class.java) {
@@ -25,11 +25,11 @@ data class SatRadio(
@PrimaryKey val uuid: String, @PrimaryKey val uuid: String,
val info: String, val info: String,
val isAlive: Boolean, val isAlive: Boolean,
var downlinkLow: Long?, val downlinkLow: Long?,
var downlinkHigh: Long?, val downlinkHigh: Long?,
val downlinkMode: String?, val downlinkMode: String?,
var uplinkLow: Long?, val uplinkLow: Long?,
var uplinkHigh: Long?, val uplinkHigh: Long?,
val uplinkMode: String?, val uplinkMode: String?,
val isInverted: Boolean, val isInverted: Boolean,
val catnum: Int? val catnum: Int?
@@ -17,16 +17,20 @@
*/ */
package com.rtbishop.look4sat.core.domain.model package com.rtbishop.look4sat.core.domain.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class SatRadio( data class SatRadio(
val uuid: String, @SerialName("uuid") val uuid: String,
val info: String, @SerialName("description") val info: String,
val isAlive: Boolean, @SerialName("alive") val isAlive: Boolean,
var downlinkLow: Long?, @SerialName("downlink_low") val downlinkLow: Long?,
var downlinkHigh: Long?, @SerialName("downlink_high") val downlinkHigh: Long?,
val downlinkMode: String?, @SerialName("mode") val downlinkMode: String?,
var uplinkLow: Long?, @SerialName("uplink_low") val uplinkLow: Long?,
var uplinkHigh: Long?, @SerialName("uplink_high") val uplinkHigh: Long?,
val uplinkMode: String?, @SerialName("uplink_mode") val uplinkMode: String?,
val isInverted: Boolean, @SerialName("invert") val isInverted: Boolean,
val catnum: Int? @SerialName("norad_cat_id") val catnum: Int?
) )
@@ -21,13 +21,19 @@ import com.rtbishop.look4sat.core.domain.model.SatRadio
import com.rtbishop.look4sat.core.domain.predict.OrbitalData import com.rtbishop.look4sat.core.domain.predict.OrbitalData
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONArray import kotlinx.serialization.json.Json
import org.json.JSONObject import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement
import java.io.InputStream import java.io.InputStream
import kotlin.math.pow import kotlin.math.pow
class DataParser(private val dispatcher: CoroutineDispatcher) { class DataParser(private val dispatcher: CoroutineDispatcher) {
private val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
suspend fun parseCSVStream(stream: InputStream): List<OrbitalData> = withContext(dispatcher) { suspend fun parseCSVStream(stream: InputStream): List<OrbitalData> = withContext(dispatcher) {
stream.bufferedReader().useLines { lines -> stream.bufferedReader().useLines { lines ->
lines.drop(1).mapNotNull { parseCSV(it.split(",")) }.toList() lines.drop(1).mapNotNull { parseCSV(it.split(",")) }.toList()
@@ -43,8 +49,12 @@ class DataParser(private val dispatcher: CoroutineDispatcher) {
suspend fun parseJSONStream(stream: InputStream): List<SatRadio> = withContext(dispatcher) { suspend fun parseJSONStream(stream: InputStream): List<SatRadio> = withContext(dispatcher) {
runCatching { runCatching {
val jsonArray = JSONArray(stream.bufferedReader().readText()) val root = json.parseToJsonElement(stream.bufferedReader().readText())
(0 until jsonArray.length()).mapNotNull { parseJSON(jsonArray.getJSONObject(it)) } (root as? JsonArray)?.mapNotNull { element ->
runCatching { json.decodeFromJsonElement<SatRadio>(element) }
.onFailure { println("JSON parsing exception: $it") }
.getOrNull()
} ?: emptyList()
}.getOrDefault(emptyList()) }.getOrDefault(emptyList())
} }
@@ -95,26 +105,6 @@ class DataParser(private val dispatcher: CoroutineDispatcher) {
) )
}.onFailure { println("TLE parsing exception: $it") }.getOrNull() }.onFailure { println("TLE parsing exception: $it") }.getOrNull()
private fun parseJSON(json: JSONObject): SatRadio? = runCatching {
SatRadio(
uuid = json.getString("uuid"),
info = json.getString("description"),
isAlive = json.getBoolean("alive"),
downlinkLow = json.optLongOrNull("downlink_low"),
downlinkHigh = json.optLongOrNull("downlink_high"),
downlinkMode = json.optStringOrNull("mode"),
uplinkLow = json.optLongOrNull("uplink_low"),
uplinkHigh = json.optLongOrNull("uplink_high"),
uplinkMode = json.optStringOrNull("uplink_mode"),
isInverted = json.getBoolean("invert"),
catnum = json.optIntOrNull("norad_cat_id")
)
}.onFailure { println("JSON parsing exception: $it") }.getOrNull()
private fun JSONObject.optStringOrNull(key: String): String? = if (isNull(key)) null else getString(key)
private fun JSONObject.optLongOrNull(key: String): Long? = if (isNull(key)) null else getLong(key)
private fun JSONObject.optIntOrNull(key: String): Int? = if (isNull(key)) null else getInt(key)
private fun getDayOfYear(year: Int, month: Int, dayOfMonth: Int): Int { private fun getDayOfYear(year: Int, month: Int, dayOfMonth: Int): Int {
val daysInMonth = intArrayOf(31, if (isLeapYear(year)) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) val daysInMonth = intArrayOf(31, if (isLeapYear(year)) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
return daysInMonth.take(month - 1).sum() + dayOfMonth return daysInMonth.take(month - 1).sum() + dayOfMonth
@@ -2,9 +2,9 @@ package com.rtbishop.look4sat.core.domain
import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.model.SatRadio
import com.rtbishop.look4sat.core.domain.utility.TransponderMapper import com.rtbishop.look4sat.core.domain.utility.TransponderMapper
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class TransponderMapperTest { class TransponderMapperTest {
@@ -1,10 +1,43 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.core.presentation package com.rtbishop.look4sat.core.presentation
sealed class Screen(val route: String, val iconResId: Int, val titleResId: Int) { import androidx.navigation3.runtime.NavKey
data object Satellites : Screen("satellites", R.drawable.ic_satellites, R.string.nav_sat) import kotlinx.serialization.Serializable
data object Passes : Screen("passes", R.drawable.ic_passes, R.string.nav_pass)
data object Radar : Screen("radar", R.drawable.ic_radar, R.string.nav_radar) @Serializable
data object Map : Screen("map", R.drawable.ic_map, R.string.nav_map) sealed class Screen(val iconResId: Int, val titleResId: Int) : NavKey {
data object Settings : Screen("settings", R.drawable.ic_settings, R.string.nav_prefs)
data object RadioControl : Screen("radiocontrol", R.drawable.ic_radios, R.string.nav_radiocontrol) @Serializable
data object Satellites : Screen(R.drawable.ic_satellites, R.string.nav_sat)
@Serializable
data object Passes : Screen(R.drawable.ic_passes, R.string.nav_pass)
@Serializable
data class Radar(val catNum: Int = 0, val aosTime: Long = 0L) : Screen(R.drawable.ic_radar, R.string.nav_radar)
@Serializable
data class RadioControl(val catNum: Int = 0, val aosTime: Long = 0L) : Screen(0, 0)
@Serializable
data object Map : Screen(R.drawable.ic_map, R.string.nav_map)
@Serializable
data object Settings : Screen(R.drawable.ic_settings, R.string.nav_prefs)
} }
@@ -60,15 +60,12 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.rtbishop.look4sat.core.domain.predict.GeoPos import com.rtbishop.look4sat.core.domain.predict.GeoPos
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
import com.rtbishop.look4sat.core.presentation.IconCard import com.rtbishop.look4sat.core.presentation.IconCard
import com.rtbishop.look4sat.core.presentation.NextPassRow import com.rtbishop.look4sat.core.presentation.NextPassRow
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.TimerRow import com.rtbishop.look4sat.core.presentation.TimerRow
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.isVerticalLayout import com.rtbishop.look4sat.core.presentation.isVerticalLayout
@@ -110,14 +107,16 @@ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
} }
private val iconCache = LruCache<String, Drawable>(128) private val iconCache = LruCache<String, Drawable>(128)
fun NavGraphBuilder.mapDestination() { @Composable
composable(Screen.Map.route) { fun MapDestination() {
val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory) val viewModel = viewModel(
modelClass = MapViewModel::class.java,
factory = MapViewModel.Factory
)
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val mapView = rememberMapViewWithLifecycle() val mapView = rememberMapViewWithLifecycle()
MapScreen(uiState, viewModel::onAction, mapView) MapScreen(uiState, viewModel::onAction, mapView)
} }
}
@Composable @Composable
private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: MapView) { private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: MapView) {
@@ -56,8 +56,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.rtbishop.look4sat.core.domain.predict.DeepSpaceObject import com.rtbishop.look4sat.core.domain.predict.DeepSpaceObject
import com.rtbishop.look4sat.core.domain.predict.NearEarthObject import com.rtbishop.look4sat.core.domain.predict.NearEarthObject
import com.rtbishop.look4sat.core.domain.predict.OrbitalData import com.rtbishop.look4sat.core.domain.predict.OrbitalData
@@ -68,7 +66,6 @@ import com.rtbishop.look4sat.core.presentation.InfoDialog
import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.MainTheme
import com.rtbishop.look4sat.core.presentation.NextPassRow import com.rtbishop.look4sat.core.presentation.NextPassRow
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.ScreenColumn import com.rtbishop.look4sat.core.presentation.ScreenColumn
import com.rtbishop.look4sat.core.presentation.TimerRow import com.rtbishop.look4sat.core.presentation.TimerRow
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
@@ -79,8 +76,8 @@ import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) { @Composable
composable(Screen.Passes.route) { fun PassesDestination(navigateToRadar: (Int, Long) -> Unit) {
val viewModel = viewModel( val viewModel = viewModel(
modelClass = PassesViewModel::class.java, modelClass = PassesViewModel::class.java,
factory = PassesViewModel.Factory factory = PassesViewModel.Factory
@@ -88,7 +85,6 @@ fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
PassesScreen(uiState, viewModel::onAction, navigateToRadar) PassesScreen(uiState, viewModel::onAction, navigateToRadar)
} }
}
@Composable @Composable
private fun PassesScreen( private fun PassesScreen(
@@ -42,6 +42,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -58,9 +59,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.model.SatRadio
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
import com.rtbishop.look4sat.core.domain.utility.toDegrees import com.rtbishop.look4sat.core.domain.utility.toDegrees
@@ -69,7 +67,6 @@ import com.rtbishop.look4sat.core.presentation.IconCard
import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.MainTheme
import com.rtbishop.look4sat.core.presentation.NextPassRow import com.rtbishop.look4sat.core.presentation.NextPassRow
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.TimerRow import com.rtbishop.look4sat.core.presentation.TimerRow
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.getDefaultPass import com.rtbishop.look4sat.core.presentation.getDefaultPass
@@ -77,21 +74,21 @@ import com.rtbishop.look4sat.core.presentation.infiniteMarquee
import com.rtbishop.look4sat.core.presentation.isVerticalLayout import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import com.rtbishop.look4sat.core.presentation.layoutPadding import com.rtbishop.look4sat.core.presentation.layoutPadding
fun NavGraphBuilder.radarDestination( @Composable
fun RadarDestination(
catNum: Int = 0,
aosTime: Long = 0L,
navigateUp: () -> Unit, navigateUp: () -> Unit,
navigateToRadioControl: (Int, Long) -> Unit = { _, _ -> } navigateToRadioControl: (Int, Long) -> Unit = { _, _ -> }
) { ) {
val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}" val viewModel = viewModel(
val radarArgs = listOf( modelClass = RadarViewModel::class.java,
navArgument("catNum") { defaultValue = 0 }, key = "$catNum-$aosTime",
navArgument("aosTime") { defaultValue = 0L } factory = RadarViewModel.factory(catNum, aosTime)
) )
composable(radarRoute, radarArgs) {
val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory)
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
RadarScreen(uiState, viewModel::onAction, navigateUp, navigateToRadioControl) RadarScreen(uiState, viewModel::onAction, navigateUp, navigateToRadioControl)
} }
}
@Composable @Composable
private fun RadarScreen( private fun RadarScreen(
@@ -101,7 +98,7 @@ private fun RadarScreen(
navigateToRadioControl: (Int, Long) -> Unit navigateToRadioControl: (Int, Long) -> Unit
) { ) {
val upcomingPass = uiState.currentPass ?: getDefaultPass() val upcomingPass = uiState.currentPass ?: getDefaultPass()
if (upcomingPass.losTime < System.currentTimeMillis()) navigateUp() LaunchedEffect(uiState.isLos) { if (uiState.isLos) navigateUp() }
val addToCalendar: () -> Unit = { val addToCalendar: () -> Unit = {
uiState.currentPass?.let { pass -> uiState.currentPass?.let { pass ->
@@ -123,14 +120,14 @@ private fun RadarScreen(
if (isVertical) { if (isVertical) {
TopBar { TopBar {
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar) IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isCurrentTimeAos) TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
IconCard(action = openRadioControl, resId = R.drawable.ic_radios) IconCard(action = openRadioControl, resId = R.drawable.ic_radios)
} }
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) } TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
} else { } else {
TopBar { TopBar {
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar) IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isCurrentTimeAos) TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc) NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
IconCard(action = openRadioControl, resId = R.drawable.ic_radios) IconCard(action = openRadioControl, resId = R.drawable.ic_radios)
} }
@@ -149,8 +146,8 @@ private fun RadarScreen(
@Composable @Composable
private fun RadarCard(uiState: RadarState, modifier: Modifier = Modifier) { private fun RadarCard(uiState: RadarState, modifier: Modifier = Modifier) {
val isEclipsed = uiState.orbitalPos?.eclipsed == true val satellitePos = uiState.orbitalPos
val borderModifier = if (isEclipsed) { val borderModifier = if (satellitePos?.aboveHorizon == true && satellitePos.eclipsed) {
val infiniteTransition = rememberInfiniteTransition(label = "eclipsedBorder") val infiniteTransition = rememberInfiniteTransition(label = "eclipsedBorder")
val borderAlpha by infiniteTransition.animateFloat( val borderAlpha by infiniteTransition.animateFloat(
initialValue = 1.0f, initialValue = 1.0f,
@@ -24,7 +24,8 @@ import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
data class RadarState( data class RadarState(
val currentPass: OrbitalPass? = null, val currentPass: OrbitalPass? = null,
val currentTime: String = "00:00:00", val currentTime: String = "00:00:00",
val isCurrentTimeAos: Boolean = true, val isTimeAos: Boolean = true,
val isLos: Boolean = false,
val isUtc: Boolean = false, val isUtc: Boolean = false,
val orientationValues: Pair<Float, Float> = 0f to 0f, val orientationValues: Pair<Float, Float> = 0f to 0f,
val orbitalPos: OrbitalPos? = null, val orbitalPos: OrbitalPos? = null,
@@ -17,10 +17,8 @@
*/ */
package com.rtbishop.look4sat.feature.radar package com.rtbishop.look4sat.feature.radar
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
@@ -45,7 +43,8 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class RadarViewModel( class RadarViewModel(
private val savedStateHandle: SavedStateHandle, private val catNum: Int,
private val aosTime: Long,
private val bluetoothReporter: IReporter, private val bluetoothReporter: IReporter,
private val networkReporter: IReporter, private val networkReporter: IReporter,
private val satelliteRepo: ISatelliteRepo, private val satelliteRepo: ISatelliteRepo,
@@ -85,8 +84,6 @@ class RadarViewModel(
} }
// Resolve which pass we're tracking and start the tick loop // Resolve which pass we're tracking and start the tick loop
viewModelScope.launch { viewModelScope.launch {
val catNum = savedStateHandle.get<Int>("catNum") ?: 0
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
val passes = satelliteRepo.passes.value val passes = satelliteRepo.passes.value
val currentPass = passes.find { it.catNum == catNum && it.aosTime == aosTime } val currentPass = passes.find { it.catNum == catNum && it.aosTime == aosTime }
?: passes.firstOrNull() ?: passes.firstOrNull()
@@ -105,7 +102,8 @@ class RadarViewModel(
val timeNow = System.currentTimeMillis() val timeNow = System.currentTimeMillis()
val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow) val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow)
val (time, isAos) = computeTimer(satPass.isDeepSpace, satPass.aosTime, satPass.losTime, timeNow) val (time, isAos) = computeTimer(satPass.isDeepSpace, satPass.aosTime, satPass.losTime, timeNow)
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = isAos, orbitalPos = pos) } val isLos = !satPass.isDeepSpace && timeNow > satPass.losTime
_uiState.update { it.copy(currentTime = time, isTimeAos = isAos, isLos = isLos, orbitalPos = pos) }
processRadios(transmitters, satPass.orbitalObject, timeNow) processRadios(transmitters, satPass.orbitalObject, timeNow)
sendPassData(pos) sendPassData(pos)
delay(1000) delay(1000)
@@ -204,18 +202,19 @@ class RadarViewModel(
} }
companion object { companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory { fun factory(catNum: Int, aosTime: Long): ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer { initializer {
val container = (this[applicationKey] as IContainerProvider).getMainContainer() val container = (this[applicationKey] as IContainerProvider).getMainContainer()
RadarViewModel( RadarViewModel(
createSavedStateHandle(), catNum = catNum,
container.provideBluetoothReporter(), aosTime = aosTime,
container.provideNetworkReporter(), bluetoothReporter = container.provideBluetoothReporter(),
container.satelliteRepo, networkReporter = container.provideNetworkReporter(),
container.settingsRepo, satelliteRepo = container.satelliteRepo,
container.provideSensorsRepo(), settingsRepo = container.settingsRepo,
container.provideAddToCalendar() sensorsRepo = container.provideSensorsRepo(),
addToCalendar = container.provideAddToCalendar()
) )
} }
} }
@@ -51,15 +51,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.model.SatRadio
import com.rtbishop.look4sat.core.presentation.CardButton import com.rtbishop.look4sat.core.presentation.CardButton
import com.rtbishop.look4sat.core.presentation.IconCard import com.rtbishop.look4sat.core.presentation.IconCard
import com.rtbishop.look4sat.core.presentation.NextPassRow import com.rtbishop.look4sat.core.presentation.NextPassRow
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.TimerRow import com.rtbishop.look4sat.core.presentation.TimerRow
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.getDefaultPass import com.rtbishop.look4sat.core.presentation.getDefaultPass
@@ -67,18 +63,16 @@ import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import com.rtbishop.look4sat.core.presentation.layoutPadding import com.rtbishop.look4sat.core.presentation.layoutPadding
import java.util.Locale import java.util.Locale
fun NavGraphBuilder.radioControlDestination(navigateUp: () -> Unit) { @Composable
val route = "${Screen.RadioControl.route}?catNum={catNum}&aosTime={aosTime}" fun RadioControlDestination(catNum: Int = 0, aosTime: Long = 0L, navigateUp: () -> Unit) {
val args = listOf( val viewModel = viewModel(
navArgument("catNum") { defaultValue = 0 }, modelClass = RadioControlViewModel::class.java,
navArgument("aosTime") { defaultValue = 0L } key = "$catNum-$aosTime",
factory = RadioControlViewModel.factory(catNum, aosTime)
) )
composable(route, args) {
val viewModel = viewModel(RadioControlViewModel::class.java, factory = RadioControlViewModel.Factory)
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
RadioControlScreen(uiState, viewModel::onAction, navigateUp) RadioControlScreen(uiState, viewModel::onAction, navigateUp)
} }
}
@Composable @Composable
private fun RadioControlScreen( private fun RadioControlScreen(
@@ -17,10 +17,8 @@
*/ */
package com.rtbishop.look4sat.feature.radiocontrol package com.rtbishop.look4sat.feature.radiocontrol
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
@@ -41,7 +39,8 @@ import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
class RadioControlViewModel( class RadioControlViewModel(
savedStateHandle: SavedStateHandle, private val catNum: Int,
private val aosTime: Long,
private val trackingService: IRadioTrackingService, private val trackingService: IRadioTrackingService,
private val satelliteRepo: ISatelliteRepo, private val satelliteRepo: ISatelliteRepo,
settingsRepo: ISettingsRepo settingsRepo: ISettingsRepo
@@ -72,9 +71,6 @@ class RadioControlViewModel(
val uiState: StateFlow<RadioControlState> = _uiState val uiState: StateFlow<RadioControlState> = _uiState
init { init {
val catNum = savedStateHandle.get<Int>("catNum") ?: 0
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
// Resolve pass and load transponders // Resolve pass and load transponders
viewModelScope.launch { viewModelScope.launch {
val passes = satelliteRepo.passes.value val passes = satelliteRepo.passes.value
@@ -190,15 +186,16 @@ class RadioControlViewModel(
return String.format(Locale.ENGLISH, "%d.%03d.%03d", mhz, khz, hz) return String.format(Locale.ENGLISH, "%d.%03d.%03d", mhz, khz, hz)
} }
val Factory: ViewModelProvider.Factory = viewModelFactory { fun factory(catNum: Int, aosTime: Long): ViewModelProvider.Factory = viewModelFactory {
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer { initializer {
val container = (this[applicationKey] as IContainerProvider).getMainContainer() val container = (this[applicationKey] as IContainerProvider).getMainContainer()
RadioControlViewModel( RadioControlViewModel(
createSavedStateHandle(), catNum = catNum,
container.radioTrackingService, aosTime = aosTime,
container.satelliteRepo, trackingService = container.radioTrackingService,
container.settingsRepo satelliteRepo = container.satelliteRepo,
settingsRepo = container.settingsRepo
) )
} }
} }
@@ -56,8 +56,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.rtbishop.look4sat.core.domain.model.SatItem import com.rtbishop.look4sat.core.domain.model.SatItem
import com.rtbishop.look4sat.core.presentation.CardLoadingIndicator import com.rtbishop.look4sat.core.presentation.CardLoadingIndicator
import com.rtbishop.look4sat.core.presentation.EmptyListCard import com.rtbishop.look4sat.core.presentation.EmptyListCard
@@ -66,21 +64,20 @@ import com.rtbishop.look4sat.core.presentation.InfoDialog
import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.MainTheme
import com.rtbishop.look4sat.core.presentation.PrimaryIconCard import com.rtbishop.look4sat.core.presentation.PrimaryIconCard
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.infiniteMarquee import com.rtbishop.look4sat.core.presentation.infiniteMarquee
import com.rtbishop.look4sat.core.presentation.isVerticalLayout import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import com.rtbishop.look4sat.core.presentation.layoutPadding import com.rtbishop.look4sat.core.presentation.layoutPadding
fun NavGraphBuilder.satellitesDestination(navigateUp: () -> Unit) { @Composable
composable(Screen.Satellites.route) { fun SatellitesDestination(navigateUp: () -> Unit) {
val viewModel = viewModel( val viewModel = viewModel(
modelClass = SatellitesViewModel::class.java, factory = SatellitesViewModel.Factory modelClass = SatellitesViewModel::class.java,
factory = SatellitesViewModel.Factory
) )
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
SatellitesScreen(uiState, viewModel::onAction, navigateUp) SatellitesScreen(uiState, viewModel::onAction, navigateUp)
} }
}
@Composable @Composable
private fun SatellitesScreen( private fun SatellitesScreen(
@@ -60,8 +60,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.rtbishop.look4sat.core.domain.model.OtherSettings import com.rtbishop.look4sat.core.domain.model.OtherSettings
import com.rtbishop.look4sat.core.domain.predict.GeoPos import com.rtbishop.look4sat.core.domain.predict.GeoPos
import com.rtbishop.look4sat.core.presentation.CardButton import com.rtbishop.look4sat.core.presentation.CardButton
@@ -69,7 +67,6 @@ import com.rtbishop.look4sat.core.presentation.IconCard
import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.MainTheme
import com.rtbishop.look4sat.core.presentation.PrimaryIconCard import com.rtbishop.look4sat.core.presentation.PrimaryIconCard
import com.rtbishop.look4sat.core.presentation.R import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.core.presentation.Screen
import com.rtbishop.look4sat.core.presentation.ScreenColumn import com.rtbishop.look4sat.core.presentation.ScreenColumn
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.infiniteMarquee import com.rtbishop.look4sat.core.presentation.infiniteMarquee
@@ -78,8 +75,8 @@ import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
fun NavGraphBuilder.settingsDestination() { @Composable
composable(Screen.Settings.route) { fun SettingsDestination() {
val viewModel = viewModel( val viewModel = viewModel(
modelClass = SettingsViewModel::class.java, modelClass = SettingsViewModel::class.java,
factory = SettingsViewModel.Factory factory = SettingsViewModel.Factory
@@ -87,7 +84,6 @@ fun NavGraphBuilder.settingsDestination() {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SettingsScreen(uiState, viewModel::onAction) SettingsScreen(uiState, viewModel::onAction)
} }
}
@Composable @Composable
private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> Unit) { private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> Unit) {
+24 -25
View File
@@ -1,8 +1,8 @@
[versions] [versions]
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
appVersionCode = "421" appVersionCode = "422"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
appVersionName = "4.2.1" appVersionName = "4.2.2"
#noinspection GradleDependency,UnusedVersionCatalogEntry #noinspection GradleDependency,UnusedVersionCatalogEntry
compileSdk = "36" compileSdk = "36"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
@@ -12,21 +12,23 @@ jdkVersion = "17"
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
packageName = "com.rtbishop.look4sat" packageName = "com.rtbishop.look4sat"
android-gradle-plugin = "9.1.1" android-gradle-plugin = "9.2.0"
google-ksp = "2.3.6"
kotlin = "2.3.20"
androidx-core-ktx = "1.18.0" androidx-core-ktx = "1.18.0"
androidx-core-splashscreen = "1.2.0" androidx-core-splashscreen = "1.2.0"
androidx-room = "2.8.4" androidx-room = "2.8.4"
compose-bom = "2026.03.01" compose-bom = "2026.04.01"
compose-activity = "1.13.0" compose-activity = "1.13.0"
compose-lifecycle = "2.10.0" compose-lifecycle = "2.10.0"
compose-navigation = "2.9.7" compose-navigation3 = "1.1.1"
google-ksp = "2.3.6"
kotlin = "2.3.20"
kotlin-coroutines = "1.10.2"
kotlin-serialization = "1.11.0"
other-coroutines = "1.10.2"
other-json = "20251224"
other-okhttp = "5.3.2" other-okhttp = "5.3.2"
other-osmdroid = "6.1.20" other-osmdroid = "6.1.20"
@@ -50,32 +52,32 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-animation = { group = "androidx.compose.animation", name = "animation" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" } compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-material3-navigation = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" } #noinspection UnusedVersionCatalogEntry
compose-material3-adaptive = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" }
compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling-preview" } compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" } compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" }
compose-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle" } #noinspection UnusedVersionCatalogEntry
compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "compose-navigation" } compose-navigation3 = { module = "androidx.navigation3:navigation3-ui", version.ref = "compose-navigation3" }
compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "compose-lifecycle" } compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "compose-lifecycle" }
compose-debug-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } compose-debug-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
compose-debug-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } compose-debug-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } #noinspection UnusedVersionCatalogEntry
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
#noinspection UnusedVersionCatalogEntry
kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" }
#noinspection UnusedVersionCatalogEntry
other-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "other-coroutines" }
#noinspection UnusedVersionCatalogEntry
other-json = { module = "org.json:json", version.ref = "other-json" }
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
other-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "other-okhttp" } other-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "other-okhttp" }
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
other-osmdroid = { module = "org.osmdroid:osmdroid-android", version.ref = "other-osmdroid" } other-osmdroid = { module = "org.osmdroid:osmdroid-android", version.ref = "other-osmdroid" }
test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "other-coroutines" } test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
test-junit4 = { module = "junit:junit", version.ref = "test-junit4" } test-junit4 = { module = "junit:junit", version.ref = "test-junit4" }
androidTest-junit = { module = "androidx.test.ext:junit", version.ref = "androidTest-junit" } androidTest-junit = { module = "androidx.test.ext:junit", version.ref = "androidTest-junit" }
@@ -87,6 +89,7 @@ android-library = { id = "com.android.library", version.ref = "android-gradle-pl
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
google-ksp = { id = "com.google.devtools.ksp", version.ref = "google-ksp" } google-ksp = { id = "com.google.devtools.ksp", version.ref = "google-ksp" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
# plugins defined by this project # plugins defined by this project
convention-applicationPlugin = { id = "com.rtbishop.look4sat.convention.applicationPlugin" } convention-applicationPlugin = { id = "com.rtbishop.look4sat.convention.applicationPlugin" }
convention-coreDataPlugin = { id = "com.rtbishop.look4sat.convention.coreDataPlugin" } convention-coreDataPlugin = { id = "com.rtbishop.look4sat.convention.coreDataPlugin" }
@@ -96,11 +99,7 @@ convention-featurePlugin = { id = "com.rtbishop.look4sat.convention.featurePlugi
[bundles] [bundles]
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
composeAll = [ composeAll = ["compose-runtime", "compose-tooling", "compose-activity", "compose-material3", "compose-viewmodel"]
"compose-animation", "compose-runtime", "compose-tooling",
"compose-activity", "compose-lifecycle", "compose-material3",
"compose-material3-navigation", "compose-navigation", "compose-viewmodel"
]
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry
composeDebug = ["compose-debug-manifest", "compose-debug-tooling"] composeDebug = ["compose-debug-manifest", "compose-debug-tooling"]
#noinspection UnusedVersionCatalogEntry #noinspection UnusedVersionCatalogEntry