Implemented EclipsedIndicator and MapScroll on select

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:47 +00:00
1 parent fdbdc513f1
commit 01139f76e7
6 files changed
+147 -100

No files matched your search

@@ -20,6 +20,7 @@ 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.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -82,19 +83,20 @@ fun NavGraphBuilder.mapDestination() {
composable(Screen.Map.route) { composable(Screen.Map.route) {
val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory) val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory)
val uiState = viewModel.uiState.collectAsStateWithLifecycle() val uiState = viewModel.uiState.collectAsStateWithLifecycle()
MapScreen(uiState) val mapView = rememberMapViewWithLifecycle()
MapScreen(uiState, mapView)
} }
} }
@Composable @Composable
private fun MapScreen(uiState: State<MapState>) { private fun MapScreen(uiState: State<MapState>, mapView: MapView) {
val onItemClick = { item: OrbitalObject -> uiState.value.sendAction(MapAction.SelectItem(item)) } val onItemClick = { item: OrbitalObject -> uiState.value.sendAction(MapAction.SelectItem(item)) }
val selectPrev = { uiState.value.sendAction(MapAction.SelectPrev) } val selectPrev = { uiState.value.sendAction(MapAction.SelectPrev) }
val selectNext = { uiState.value.sendAction(MapAction.SelectNext) } val selectNext = { uiState.value.sendAction(MapAction.SelectNext) }
val rotateMod = Modifier.rotate(180f) val rotateMod = Modifier.rotate(180f)
val timeString = uiState.value.mapData?.aosTime ?: "00:00:00" val timeString = uiState.value.mapData?.aosTime ?: "00:00:00"
val isTimeAos = uiState.value.mapData?.isTimeAos ?: true val isTimeAos = uiState.value.mapData?.isTimeAos ?: true
val osmInfo = "Wikimedia maps | Map data © OpenStreetMap contributors" val osmInfo = "© OpenStreetMap contributors"
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
TimerRow { TimerRow {
CardIcon(onClick = selectPrev, iconId = R.drawable.ic_arrow, modifier = rotateMod) CardIcon(onClick = selectPrev, iconId = R.drawable.ic_arrow, modifier = rotateMod)
@@ -104,7 +106,12 @@ private fun MapScreen(uiState: State<MapState>) {
NextPassRow(pass = uiState.value.orbitalPass) NextPassRow(pass = uiState.value.orbitalPass)
ElevatedCard(modifier = Modifier.weight(1f)) { ElevatedCard(modifier = Modifier.weight(1f)) {
Box(contentAlignment = Alignment.BottomCenter) { Box(contentAlignment = Alignment.BottomCenter) {
MapView(isLightUi = uiState.value.isLightUi) { mapView -> LaunchedEffect(uiState.value.track) {
val latitude = uiState.value.track?.get(0)?.get(0)?.latitude ?: 0.0
val longitude = uiState.value.track?.get(0)?.get(0)?.longitude ?: 0.0
mapView.controller.animateTo(GeoPoint(latitude, longitude))
}
AndroidView({ mapView }) { mapView ->
uiState.value.stationPosition?.let { setStationPosition(it, mapView) } uiState.value.stationPosition?.let { setStationPosition(it, mapView) }
uiState.value.positions?.let { setPositions(it, mapView, onItemClick) } uiState.value.positions?.let { setPositions(it, mapView, onItemClick) }
uiState.value.track?.let { setSatelliteTrack(it, mapView) } uiState.value.track?.let { setSatelliteTrack(it, mapView) }
@@ -207,7 +214,6 @@ private fun getCustomTextIcon(textLabel: String, mapView: MapView): Drawable {
} }
private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) { private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
// val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon)
val trackOverlay = FolderOverlay() val trackOverlay = FolderOverlay()
try { try {
satTrack.forEach { track -> satTrack.forEach { track ->
@@ -222,7 +228,6 @@ private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
} catch (exception: Exception) { } catch (exception: Exception) {
println(exception) println(exception)
} }
// mapView.controller.animateTo(center)
} }
private fun setFootprint(orbitalPos: OrbitalPos, mapView: MapView) { private fun setFootprint(orbitalPos: OrbitalPos, mapView: MapView) {
@@ -239,17 +244,7 @@ private fun setFootprint(orbitalPos: OrbitalPos, mapView: MapView) {
} }
@Composable @Composable
private fun MapView( private fun rememberMapViewWithLifecycle(): MapView {
modifier: Modifier = Modifier,
isLightUi: Boolean,
update: ((map: MapView) -> Unit)? = null
) {
val mapView = rememberMapViewWithLifecycle(isLightUi)
AndroidView({ mapView }, modifier) { update?.invoke(it) }
}
@Composable
private fun rememberMapViewWithLifecycle(isLightTheme: Boolean): MapView {
val tileSource = XYTileSource("tiles", 0, 6, 256, ".webp", emptyArray<String>()) val tileSource = XYTileSource("tiles", 0, 6, 256, ".webp", emptyArray<String>())
val context = LocalContext.current val context = LocalContext.current
val mapView = remember { MapView(context) }.apply { val mapView = remember { MapView(context) }.apply {
@@ -263,7 +258,7 @@ private fun rememberMapViewWithLifecycle(isLightTheme: Boolean): MapView {
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
overlayManager.tilesOverlay.setColorFilter(getColorFilter(isLightTheme)) overlayManager.tilesOverlay.setColorFilter(getColorFilter())
setScrollableAreaLimitLatitude(maxLat, minLat, 0) setScrollableAreaLimitLatitude(maxLat, minLat, 0)
// add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons // add overlays: 0 - GSP, 1 - SatTrack, 2 - SatFootprint, 3 - SatIcons
overlays.addAll(Array(4) { FolderOverlay() }) overlays.addAll(Array(4) { FolderOverlay() })
@@ -290,13 +285,13 @@ private fun rememberMapViewLifecycleObserver(mapView: MapView) = remember(mapVie
} }
@Composable @Composable
private fun getColorFilter(isLightTheme: Boolean): ColorMatrixColorFilter { private fun getColorFilter(): ColorMatrixColorFilter {
val grayScale = ColorMatrix().apply { setSaturation(0f) } val grayScale = ColorMatrix().apply { setSaturation(0f) }
val negative = ColorMatrix( val negative = ColorMatrix(
floatArrayOf(-1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f) floatArrayOf(-1f, 0f, 0f, 0f, 260f, 0f, -1f, 0f, 0f, 260f, 0f, 0f, -1f, 0f, 260f, 0f, 0f, 0f, 1f, 0f)
) )
negative.preConcat(grayScale) negative.preConcat(grayScale)
return if (isLightTheme) ColorMatrixColorFilter(grayScale) else ColorMatrixColorFilter(negative) return ColorMatrixColorFilter(negative)
} }
private fun getMinZoom(screenHeight: Int): Double { private fun getMinZoom(screenHeight: Int): Double {
@@ -33,6 +33,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@@ -60,7 +61,7 @@ fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) {
modelClass = PassesViewModel::class.java, modelClass = PassesViewModel::class.java,
factory = PassesViewModel.Factory factory = PassesViewModel.Factory
) )
val uiState = viewModel.uiState.value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
PassesScreen(uiState, navigateToRadar) PassesScreen(uiState, navigateToRadar)
} }
} }
@@ -17,8 +17,6 @@
*/ */
package com.rtbishop.look4sat.presentation.passes package com.rtbishop.look4sat.presentation.passes
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -34,6 +32,9 @@ import com.rtbishop.look4sat.presentation.components.getDefaultPass
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -41,7 +42,7 @@ class PassesViewModel(
private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo
) : ViewModel() { ) : ViewModel() {
private val _uiState = mutableStateOf( private val _uiState = MutableStateFlow(
PassesState( PassesState(
isPassesDialogShown = false, isPassesDialogShown = false,
isRadiosDialogShown = false, isRadiosDialogShown = false,
@@ -58,7 +59,7 @@ class PassesViewModel(
) )
) )
private var processing: Job? = null private var processing: Job? = null
val uiState: State<PassesState> = _uiState val uiState: StateFlow<PassesState> = _uiState
init { init {
viewModelScope.launch { viewModelScope.launch {
@@ -69,7 +70,7 @@ class PassesViewModel(
val timeNow = System.currentTimeMillis() val timeNow = System.currentTimeMillis()
val newPasses = satelliteRepo.processPasses(passes, timeNow) val newPasses = satelliteRepo.processPasses(passes, timeNow)
setPassInfo(newPasses, timeNow) setPassInfo(newPasses, timeNow)
_uiState.value = _uiState.value.copy(isRefreshing = false, itemsList = newPasses) _uiState.update { it.copy(isRefreshing = false, itemsList = newPasses) }
delay(1000) delay(1000)
} }
} }
@@ -88,15 +89,15 @@ class PassesViewModel(
} }
private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List<String>) = viewModelScope.launch { private fun applyFilter(hoursAhead: Int, minElevation: Double, modes: List<String>) = viewModelScope.launch {
_uiState.value = _uiState.value.copy(isRefreshing = true) _uiState.update { it.copy(isRefreshing = true) }
processing?.cancelAndJoin() processing?.cancelAndJoin()
settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes)) settingsRepo.setPassesSettings(PassesSettings(hoursAhead, minElevation, modes))
_uiState.value = _uiState.value.copy(hours = hoursAhead, elevation = minElevation, modes = modes) _uiState.update { it.copy(hours = hoursAhead, elevation = minElevation, modes = modes) }
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes) satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
} }
private fun refreshPasses() = viewModelScope.launch { private fun refreshPasses() = viewModelScope.launch {
_uiState.value = _uiState.value.copy(isRefreshing = true) _uiState.update { it.copy(isRefreshing = true) }
processing?.cancelAndJoin() processing?.cancelAndJoin()
val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value val (hoursAhead, minElevation, modes) = settingsRepo.passesSettings.value
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes) satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation, modes)
@@ -107,22 +108,22 @@ class PassesViewModel(
try { try {
val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 } val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 }
val time = nextPass.aosTime.minus(timeNow).toTimerString() val time = nextPass.aosTime.minus(timeNow).toTimerString()
_uiState.value = _uiState.value.copy(nextPass = nextPass, nextTime = time, isNextTimeAos = true) _uiState.update { it.copy(nextPass = nextPass, nextTime = time, isNextTimeAos = true) }
} catch (exception: NoSuchElementException) { } catch (exception: NoSuchElementException) {
val lastPass = passes.last() val lastPass = passes.last()
val time = lastPass.losTime.minus(timeNow).toTimerString() val time = lastPass.losTime.minus(timeNow).toTimerString()
_uiState.value = _uiState.value.copy(nextPass = lastPass, nextTime = time, isNextTimeAos = false) _uiState.update { it.copy(nextPass = lastPass, nextTime = time, isNextTimeAos = false) }
} }
} }
private fun toggleFilterDialog() { private fun toggleFilterDialog() {
val currentDialogState = _uiState.value.isPassesDialogShown val currentDialogState = _uiState.value.isPassesDialogShown
_uiState.value = _uiState.value.copy(isPassesDialogShown = currentDialogState.not()) _uiState.update { it.copy(isPassesDialogShown = currentDialogState.not()) }
} }
private fun toggleRadiosDialog() { private fun toggleRadiosDialog() {
val currentDialogState = _uiState.value.isRadiosDialogShown val currentDialogState = _uiState.value.isRadiosDialogShown
_uiState.value = _uiState.value.copy(isRadiosDialogShown = currentDialogState.not()) _uiState.update { it.copy(isRadiosDialogShown = currentDialogState.not()) }
} }
companion object { companion object {
@@ -15,10 +15,13 @@ 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.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
@@ -101,7 +104,8 @@ private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
} }
Column( Column(
verticalArrangement = Arrangement.SpaceBetween, verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxSize() modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp) .padding(horizontal = 6.dp, vertical = 4.dp)
) { ) {
Row( Row(
@@ -135,34 +139,35 @@ private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
) )
} }
} }
if (uiState.orbitalPos.eclipsed) {
val infiniteTransition = rememberInfiniteTransition()
val textAlpha = infiniteTransition.animateFloat(
initialValue = 1.0f,
targetValue = 0.0f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 500,
delayMillis = 25,
easing = LinearEasing
),
repeatMode = RepeatMode.Reverse
))
Text(
text = "Eclipsed!",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.alpha(textAlpha.value)
.border(2.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.medium)
.padding(16.dp)
)
}
} }
} }
ElevatedCard(modifier = Modifier.fillMaxSize()) { ElevatedCard(modifier = Modifier.fillMaxSize()) {
TransmittersList(transmitters = uiState.transmitters) if (uiState.transmitters.isEmpty()) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
painter = painterResource(R.drawable.ic_satellite),
contentDescription = null,
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "This satellite doesn't have any known transcievers...",
textAlign = TextAlign.Center,
fontSize = 18.sp
)
}
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
TransmittersList(transmitters = uiState.transmitters)
if (uiState.orbitalPos?.eclipsed == true) {
EclipsedIndicator()
}
}
}
} }
} }
} }
@@ -207,6 +212,42 @@ private fun TransmitterItemPreview() {
MainTheme { TransmitterItem(transmitter) } MainTheme { TransmitterItem(transmitter) }
} }
@Composable
private fun EclipsedIndicator() {
val bgColor = MaterialTheme.colorScheme.primary
val bgShape = MaterialTheme.shapes.medium
val infiniteTransition = rememberInfiniteTransition()
val textAlpha = infiniteTransition.animateFloat(
initialValue = 1.0f,
targetValue = 0.0f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
delayMillis = 25,
easing = LinearEasing
),
repeatMode = RepeatMode.Reverse
)
)
Box(
modifier = Modifier
.fillMaxSize()
.alpha(textAlpha.value)
.border(width = 2.dp, color = bgColor, shape = bgShape),
contentAlignment = Alignment.Center
) {
Text(
text = "Eclipsed!",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.background,
modifier = Modifier
.background(color = bgColor, shape = bgShape)
.padding(12.dp)
)
}
}
@Composable @Composable
private fun TransmitterItem(radio: SatRadio) { private fun TransmitterItem(radio: SatRadio) {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
@@ -224,7 +265,9 @@ private fun TransmitterItem(radio: SatRadio) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_arrow), painter = painterResource(id = R.drawable.ic_arrow),
tint = Color.Green, tint = Color.Green,
contentDescription = null, modifier = Modifier.rotate(90f).weight(0.15f) contentDescription = null, modifier = Modifier
.rotate(90f)
.weight(0.15f)
) )
Text( Text(
text = if (radio.isInverted) "INVERTED: ${radio.info} " else "${radio.info} ", text = if (radio.isInverted) "INVERTED: ${radio.info} " else "${radio.info} ",
@@ -239,7 +282,9 @@ private fun TransmitterItem(radio: SatRadio) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_arrow), painter = painterResource(id = R.drawable.ic_arrow),
tint = Color.Red, tint = Color.Red,
contentDescription = null, modifier = Modifier.rotate(-90f).weight(0.15f) contentDescription = null, modifier = Modifier
.rotate(-90f)
.weight(0.15f)
) )
} }
FrequencyRow(satRadio = radio) FrequencyRow(satRadio = radio)
@@ -32,6 +32,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@@ -43,6 +44,7 @@ import com.rtbishop.look4sat.presentation.Screen
import com.rtbishop.look4sat.presentation.components.CardButton import com.rtbishop.look4sat.presentation.components.CardButton
import com.rtbishop.look4sat.presentation.components.gotoUrl import com.rtbishop.look4sat.presentation.components.gotoUrl
import com.rtbishop.look4sat.presentation.components.showToast import com.rtbishop.look4sat.presentation.components.showToast
import org.osmdroid.library.BuildConfig
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@@ -88,7 +90,7 @@ private fun SettingsScreen() {
} }
// Position settings // Position settings
val positionSettings = viewModel.positionSettings.value val positionSettings = viewModel.positionSettings.collectAsStateWithLifecycle().value
val stationPos = positionSettings.stationPos val stationPos = positionSettings.stationPos
val setGpsPos = { locationRequest.launch(arrayOf(locationPermCoarse, locationPermFine)) } val setGpsPos = { locationRequest.launch(arrayOf(locationPermCoarse, locationPermFine)) }
val setGeoPos = { lat: Double, lon: Double -> viewModel.setGeoPosition(lat, lon) } val setGeoPos = { lat: Double, lon: Double -> viewModel.setGeoPosition(lat, lon) }
@@ -106,13 +108,13 @@ private fun SettingsScreen() {
} }
// Data settings // Data settings
val dataSettings = viewModel.dataSettings.value val dataSettings = viewModel.dataSettings.collectAsStateWithLifecycle().value
val updateFromWeb: () -> Unit = { viewModel.updateFromWeb() } val updateFromWeb: () -> Unit = { viewModel.updateFromWeb() }
val updateFromFile = { contentRequest.launch("*/*") } val updateFromFile = { contentRequest.launch("*/*") }
val clearAllData: () -> Unit = { viewModel.clearAllData() } val clearAllData: () -> Unit = { viewModel.clearAllData() }
// Other settings // Other settings
val otherSettings = viewModel.otherSettings.value val otherSettings = viewModel.otherSettings.collectAsStateWithLifecycle().value
val toggleUtc = { value: Boolean -> viewModel.toggleUtc(value) } val toggleUtc = { value: Boolean -> viewModel.toggleUtc(value) }
val toggleUpdate = { value: Boolean -> viewModel.toggleUpdate(value) } val toggleUpdate = { value: Boolean -> viewModel.toggleUpdate(value) }
val toggleSweep = { value: Boolean -> viewModel.toggleSweep(value) } val toggleSweep = { value: Boolean -> viewModel.toggleSweep(value) }
@@ -428,13 +430,15 @@ private fun OtherCard(
Text(text = stringResource(id = R.string.other_switch_sensors)) Text(text = stringResource(id = R.string.other_switch_sensors))
Switch(checked = settings.stateOfSensors, onCheckedChange = { toggleSensor(it) }) Switch(checked = settings.stateOfSensors, onCheckedChange = { toggleSensor(it) })
} }
Row( if (BuildConfig.DEBUG) {
horizontalArrangement = Arrangement.SpaceBetween, Row(
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() verticalAlignment = Alignment.CenterVertically,
) { modifier = Modifier.fillMaxWidth()
Text(text = stringResource(id = R.string.other_switch_light_theme)) ) {
Switch(checked = settings.stateOfLightTheme, onCheckedChange = { toggleLightTheme(it) }) Text(text = stringResource(id = R.string.other_switch_light_theme))
Switch(checked = settings.stateOfLightTheme, onCheckedChange = { toggleLightTheme(it) })
}
} }
} }
} }
@@ -17,8 +17,6 @@
*/ */
package com.rtbishop.look4sat.presentation.settings package com.rtbishop.look4sat.presentation.settings
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -29,6 +27,9 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.domain.repository.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel( class SettingsViewModel(
@@ -36,89 +37,89 @@ class SettingsViewModel(
) : ViewModel() { ) : ViewModel() {
private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0) private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0)
private val _positionSettings = mutableStateOf(defaultPosSettings) private val _positionSettings = MutableStateFlow(defaultPosSettings)
val positionSettings: State<PositionSettings> = _positionSettings val positionSettings: StateFlow<PositionSettings> = _positionSettings
private val defaultDataSettings = DataSettings(false, 0, 0, 0L) private val defaultDataSettings = DataSettings(false, 0, 0, 0L)
private val _dataSettings = mutableStateOf(defaultDataSettings) private val _dataSettings = MutableStateFlow(defaultDataSettings)
val dataSettings: State<DataSettings> = _dataSettings val dataSettings: StateFlow<DataSettings> = _dataSettings
private val _otherSettings = mutableStateOf(settingsRepo.otherSettings.value) private val _otherSettings = MutableStateFlow(settingsRepo.otherSettings.value)
val otherSettings: State<OtherSettings> = _otherSettings val otherSettings: StateFlow<OtherSettings> = _otherSettings
init { init {
viewModelScope.launch { viewModelScope.launch {
settingsRepo.stationPosition.collect { geoPos -> settingsRepo.stationPosition.collect { geoPos ->
_positionSettings.value = _positionSettings.update { it.copy(isUpdating = false, stationPos = geoPos) }
_positionSettings.value.copy(isUpdating = false, stationPos = geoPos)
} }
} }
viewModelScope.launch { viewModelScope.launch {
settingsRepo.databaseState.collect { state -> settingsRepo.databaseState.collect { state ->
_dataSettings.value = _dataSettings.value.copy( _dataSettings.update {
isUpdating = false, it.copy(
entriesTotal = state.numberOfSatellites, isUpdating = false,
radiosTotal = state.numberOfRadios, entriesTotal = state.numberOfSatellites,
timestamp = state.updateTimestamp radiosTotal = state.numberOfRadios,
) timestamp = state.updateTimestamp
)
}
} }
} }
viewModelScope.launch { viewModelScope.launch {
settingsRepo.otherSettings.collect { _otherSettings.value = it } settingsRepo.otherSettings.collect { settings -> _otherSettings.update { settings } }
} }
} }
fun setGpsPosition() { fun setGpsPosition() {
if (settingsRepo.setStationPositionGps()) { if (settingsRepo.setStationPositionGps()) {
val messageResId = R.string.location_success val messageResId = R.string.location_success
_positionSettings.value = _positionSettings.update { it.copy(isUpdating = true, messageResId = messageResId) }
_positionSettings.value.copy(isUpdating = true, messageResId = messageResId)
} else { } else {
val errorResId = R.string.location_gps_error val errorResId = R.string.location_gps_error
_positionSettings.value = _positionSettings.value.copy(messageResId = errorResId) _positionSettings.update { it.copy(messageResId = errorResId) }
} }
} }
fun setGeoPosition(latitude: Double, longitude: Double) { fun setGeoPosition(latitude: Double, longitude: Double) {
if (settingsRepo.setStationPositionGeo(latitude, longitude, 0.0)) { if (settingsRepo.setStationPositionGeo(latitude, longitude, 0.0)) {
val messageResId = R.string.location_success val messageResId = R.string.location_success
_positionSettings.value = _positionSettings.value.copy(messageResId = messageResId) _positionSettings.update { it.copy(messageResId = messageResId) }
} else { } else {
val errorResId = R.string.location_manual_error val errorResId = R.string.location_manual_error
_positionSettings.value = _positionSettings.value.copy(messageResId = errorResId) _positionSettings.update { it.copy(messageResId = errorResId) }
} }
} }
fun setQthPosition(locator: String) { fun setQthPosition(locator: String) {
if (settingsRepo.setStationPositionQth(locator)) { if (settingsRepo.setStationPositionQth(locator)) {
val messageResId = R.string.location_success val messageResId = R.string.location_success
_positionSettings.value = _positionSettings.value.copy(messageResId = messageResId) _positionSettings.update { it.copy(messageResId = messageResId) }
} else { } else {
val errorResId = R.string.location_qth_error val errorResId = R.string.location_qth_error
_positionSettings.value = _positionSettings.value.copy(messageResId = errorResId) _positionSettings.update { it.copy(messageResId = errorResId) }
} }
} }
fun dismissPosMessage() { fun dismissPosMessage() {
_positionSettings.value = _positionSettings.value.copy(messageResId = 0) _positionSettings.update { it.copy(messageResId = 0) }
} }
fun updateFromWeb() = viewModelScope.launch { fun updateFromWeb() = viewModelScope.launch {
try { try {
_dataSettings.value = _dataSettings.value.copy(isUpdating = true) _dataSettings.update { it.copy(isUpdating = true) }
databaseRepo.updateFromRemote() databaseRepo.updateFromRemote()
} catch (exception: Exception) { } catch (exception: Exception) {
_dataSettings.value = _dataSettings.value.copy(isUpdating = false) _dataSettings.update { it.copy(isUpdating = false) }
println(exception) println(exception)
} }
} }
fun updateFromFile(uri: String) = viewModelScope.launch { fun updateFromFile(uri: String) = viewModelScope.launch {
try { try {
_dataSettings.value = _dataSettings.value.copy(isUpdating = true) _dataSettings.update { it.copy(isUpdating = true) }
databaseRepo.updateFromFile(uri) databaseRepo.updateFromFile(uri)
} catch (exception: Exception) { } catch (exception: Exception) {
_dataSettings.value = _dataSettings.value.copy(isUpdating = false) _dataSettings.update { it.copy(isUpdating = false) }
println(exception) println(exception)
} }
} }