diff --git a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt index 787ccedd..23a972fc 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt @@ -16,6 +16,7 @@ import com.rtbishop.look4sat.data.repository.SensorsRepo import com.rtbishop.look4sat.data.repository.SettingsRepo import com.rtbishop.look4sat.data.source.LocalSource import com.rtbishop.look4sat.data.source.RemoteSource +import com.rtbishop.look4sat.data.usecase.AddToCalendar import com.rtbishop.look4sat.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.domain.repository.ISatelliteRepo import com.rtbishop.look4sat.domain.repository.ISelectionRepo @@ -23,6 +24,7 @@ import com.rtbishop.look4sat.domain.repository.ISensorsRepo import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.source.ILocalSource import com.rtbishop.look4sat.domain.source.IRemoteSource +import com.rtbishop.look4sat.domain.usecase.IAddToCalendar import com.rtbishop.look4sat.domain.utility.DataParser import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope @@ -41,6 +43,10 @@ class MainContainer(private val context: Context) { val satelliteRepo = provideSatelliteRepo() val databaseRepo = provideDatabaseRepo() + fun provideAddToCalendar(): IAddToCalendar { + return AddToCalendar(context) + } + fun provideBluetoothReporter(): BluetoothReporter { val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO)) 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 873c4afa..7fe956a6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt @@ -16,7 +16,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable 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.infoDestination import com.rtbishop.look4sat.presentation.map.mapDestination @@ -42,14 +41,9 @@ fun MainScreen() { 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) { mainDestination(navigateToRadar) - radarDestination(radarRoute, radarArgs) + radarDestination { outerNavController.navigateUp() } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt index 3cb976da..646754f9 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/components/Common.kt @@ -80,14 +80,14 @@ fun RowScope.TimerBar(timeString: String, isTimeAos: Boolean) { horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxSize() ) { - Text(text = "AOS", fontSize = 16.sp, color = aosColor) + Text(text = "AOS", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = aosColor) Text( text = timeString, fontSize = 32.sp, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary ) - Text(text = "LOS", fontSize = 16.sp, color = losColor) + Text(text = "LOS", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = losColor) } } } 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 1504f06f..07c5aec1 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 @@ -34,6 +34,11 @@ 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 com.rtbishop.look4sat.presentation.components.CardIcon +import com.rtbishop.look4sat.presentation.components.NextPassRow +import com.rtbishop.look4sat.presentation.components.TimerBar +import com.rtbishop.look4sat.presentation.components.TimerRow +import com.rtbishop.look4sat.presentation.components.getDefaultPass import org.osmdroid.tileprovider.tilesource.XYTileSource import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController @@ -78,6 +83,12 @@ private fun MapScreen() { val footprint = viewModel.footprint.collectAsState(initial = null) val positionClick = { orbitalObject: OrbitalObject -> viewModel.selectSatellite(orbitalObject) } Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + TimerRow { + CardIcon(onClick = {}, iconId = R.drawable.ic_filter) + TimerBar(timeString = "88:88:88", isTimeAos = true) + CardIcon(onClick = {}, iconId = R.drawable.ic_satellite) + } + NextPassRow(pass = getDefaultPass()) ElevatedCard(modifier = Modifier.fillMaxSize()) { MapView(modifier = Modifier.fillMaxSize(), isLightTheme = viewModel.stateOfLightTheme) { mapView -> stationPos.value?.let { setStationPosition(it, mapView) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarData.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarData.kt deleted file mode 100644 index 72ad66e3..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarData.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.presentation.radar - -import com.rtbishop.look4sat.domain.predict.OrbitalPass -import com.rtbishop.look4sat.domain.predict.OrbitalPos - -data class RadarData( - val orbitalPass: OrbitalPass, - val orbitalPos: OrbitalPos, - val satTrack: List = emptyList() -) 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 47d3c49f..a3fb774b 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 @@ -1,9 +1,13 @@ package com.rtbishop.look4sat.presentation.radar +import androidx.compose.foundation.MarqueeSpacing import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -16,57 +20,77 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight 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.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NamedNavArgument import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable +import androidx.navigation.navArgument import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.model.SatRadio +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 +import com.rtbishop.look4sat.presentation.components.TimerRow +import com.rtbishop.look4sat.presentation.components.getDefaultPass -fun NavGraphBuilder.radarDestination(radarRoute: String, radarArgs: List) { - composable(radarRoute, radarArgs) { RadarScreen() } +fun NavGraphBuilder.radarDestination(navigateBack: () -> Unit) { + val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}" + val radarArgs = listOf( + navArgument("catNum") { defaultValue = 0 }, + navArgument("aosTime") { defaultValue = 0L } + ) + composable(radarRoute, radarArgs) { + val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory) + val uiState = viewModel.uiState.collectAsStateWithLifecycle().value + RadarScreen(uiState, navigateBack) + } } @Composable -private fun RadarScreen() { - val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory) - val currentPass = viewModel.getPass().collectAsState(null).value - val id = currentPass?.catNum ?: 99999 - val name = currentPass?.name ?: "Satellite" +private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) { + val addToCalendar: () -> Unit = { + uiState.currentPass?.let { pass -> + uiState.sendAction(RadarAction.AddToCalendar(pass.name, pass.aosTime, pass.losTime)) + } + } Scaffold { innerPadding -> + val paddingMod = Modifier.padding(innerPadding) Column( - modifier = Modifier.padding(innerPadding), + modifier = paddingMod.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp) ) { -// TimerBarNew(id, name, "88:88:88", R.drawable.ic_notifications) {} - ElevatedCard( - modifier = Modifier - .fillMaxSize() - .weight(1f) - ) { - viewModel.radarData.value?.let { data -> + TimerRow { + CardIcon(onClick = navigateBack, iconId = R.drawable.ic_back) + TimerBar(timeString = uiState.currentTime, isTimeAos = uiState.isCurrentTimeAos) + CardIcon(onClick = addToCalendar, iconId = R.drawable.ic_calendar) + } + NextPassRow(pass = uiState.currentPass ?: getDefaultPass()) + ElevatedCard(modifier = Modifier.aspectRatio(1f)) { + uiState.orbitalPos?.let { item -> RadarViewCompose( - item = data.orbitalPos, - items = data.satTrack, - azimElev = viewModel.orientation.value + item = item, + items = uiState.satTrack, + azimElev = uiState.orientationValues, + shouldShowSweep = uiState.shouldShowSweep, + shouldUseCompass = uiState.shouldUseCompass ) } } - ElevatedCard( - modifier = Modifier - .fillMaxSize() - .weight(1f) - ) { - TransmittersList(transmitters = viewModel.transmitters.value) + ElevatedCard(modifier = Modifier.fillMaxSize()) { + TransmittersList(transmitters = uiState.transmitters) } } } @@ -81,6 +105,16 @@ private fun TransmittersList(transmitters: List) { } } +@Preview +@Composable +private fun TransmitterItemPreview() { + val transmitter = SatRadio( + "", "Extremely powerful transmitter", true, 10000000000L, 10000000000L, + null, 10000000000L, 10000000000L, "FSK AX.100 Mode 5", true, 0 + ) + MainTheme { TransmitterItem(transmitter) } +} + @Composable private fun TransmitterItem(radio: SatRadio) { Surface(color = MaterialTheme.colorScheme.background) { @@ -96,55 +130,80 @@ private fun TransmitterItem(radio: SatRadio) { modifier = Modifier.fillMaxWidth() ) { Icon( - painter = painterResource(id = R.drawable.ic_arrow), - contentDescription = null, modifier = Modifier.rotate(180f) + painter = painterResource(id = R.drawable.ic_back), + tint = Color.Green, + contentDescription = null, modifier = Modifier.rotate(-90f) ) Text( - text = radio.info, - modifier = Modifier.weight(1f), - textAlign = TextAlign.Center + text = if (radio.isInverted) "INVERTED: ${radio.info} " else "${radio.info} ", + textAlign = TextAlign.Center, + modifier = Modifier + .basicMarquee( + iterations = Int.MAX_VALUE, + spacing = MarqueeSpacing(0.dp) + ) + .weight(1f) ) Icon( - painter = painterResource(id = R.drawable.ic_arrow), - contentDescription = null - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = stringResource(id = R.string.radio_downlink, radio.downlinkLow ?: 0L), - textAlign = TextAlign.Center, - fontSize = 15.sp, - modifier = Modifier.weight(0.5f) - ) - Text( - text = stringResource(id = R.string.radio_uplink, radio.uplinkLow ?: 0L), - textAlign = TextAlign.Center, - fontSize = 15.sp, - modifier = Modifier.weight(0.5f) - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = radio.downlinkMode ?: "", - fontSize = 15.sp - ) - Text( - text = radio.isInverted.toString(), - fontSize = 15.sp + painter = painterResource(id = R.drawable.ic_back), + tint = Color.Red, + contentDescription = null, modifier = Modifier.rotate(90f) ) } + FrequencyRow(satRadio = radio) } } } } +@Composable +private fun FrequencyRow(satRadio: SatRadio) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + ModeText(satRadio.downlinkMode) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.weight(0.35f) + ) { + FrequencyText(satRadio.downlinkHigh) + FrequencyText(satRadio.downlinkLow) + } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.weight(0.35f) + ) { + FrequencyText(satRadio.uplinkHigh) + FrequencyText(satRadio.uplinkLow) + } + ModeText(satRadio.uplinkMode) + } +} + +@Composable +private fun RowScope.ModeText(mode: String?) { + Text( + text = mode ?: "- - : - -", + textAlign = TextAlign.Center, + fontSize = 15.sp, + modifier = Modifier.weight(0.15f) + ) +} + +@Composable +private fun FrequencyText(frequency: Long?) { + val noLinkText = stringResource(R.string.radio_no_link) + val freqValue = frequency?.let { it / 1000000f } + val freqText = freqValue?.let { stringResource(id = R.string.radio_link_low, it) } ?: noLinkText + Text( + text = freqText, + textAlign = TextAlign.Center, + fontSize = 21.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) +} + //private val divider = 1000000f //radioDownlink.text = String.format(Locale.ENGLISH, link, downlink / divider) //radioUplink.text = String.format(Locale.ENGLISH, link, uplink / divider) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarState.kt new file mode 100644 index 00000000..325895ce --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarState.kt @@ -0,0 +1,22 @@ +package com.rtbishop.look4sat.presentation.radar + +import com.rtbishop.look4sat.domain.model.SatRadio +import com.rtbishop.look4sat.domain.predict.OrbitalPass +import com.rtbishop.look4sat.domain.predict.OrbitalPos + +data class RadarState( + val currentPass: OrbitalPass?, + val currentTime: String, + val isCurrentTimeAos: Boolean, + val orientationValues: Pair, + val orbitalPos: OrbitalPos?, + val satTrack: List, + val shouldShowSweep: Boolean, + val shouldUseCompass: Boolean, + val transmitters: List, + val sendAction: (RadarAction) -> Unit +) + +sealed class RadarAction { + data class AddToCalendar(val name: String, val aosTime: Long, val losTime: Long) : RadarAction() +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt index 8be34fd6..cfdd72ee 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarView.kt @@ -1,6 +1,5 @@ package com.rtbishop.look4sat.presentation.radar -import android.media.AudioAttributes import android.media.SoundPool import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -10,8 +9,6 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -37,20 +34,20 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.sp -import androidx.core.view.HapticFeedbackConstantsCompat -import com.rtbishop.look4sat.R import com.rtbishop.look4sat.domain.predict.OrbitalPos import com.rtbishop.look4sat.domain.predict.PI_2 import com.rtbishop.look4sat.domain.utility.toRadians -import kotlin.math.abs import kotlin.math.cos -import kotlin.math.min -import kotlin.math.pow import kotlin.math.sin -import kotlin.math.sqrt @Composable -fun RadarViewCompose(item: OrbitalPos, items: List, azimElev: Pair) { +fun RadarViewCompose( + item: OrbitalPos, + items: List, + azimElev: Pair, + shouldShowSweep: Boolean, + shouldUseCompass: Boolean +) { val context = LocalContext.current val view = LocalView.current val radarColor = MaterialTheme.colorScheme.secondary @@ -138,8 +135,8 @@ fun RadarViewCompose(item: OrbitalPos, items: List, azimElev: Pair>(emptyList()) - val radarData = mutableStateOf(null) - val orientation = mutableStateOf(sensorsRepo.orientation.value) + private val magDeclination = sensorsRepo.getMagDeclination(stationPos) + val uiState: StateFlow = _uiState init { if (settingsRepo.otherSettings.value.stateOfSensors) { viewModelScope.launch { sensorsRepo.enableSensor() sensorsRepo.orientation.collect { data -> - orientation.value = Pair(data.first + magDeclination, data.second) + val orientationValues = Pair(data.first + magDeclination, data.second) + _uiState.update { it.copy(orientationValues = orientationValues) } + } + } + } + viewModelScope.launch { + val catNum = savedStateHandle.get("catNum") ?: 0 + val aosTime = savedStateHandle.get("aosTime") ?: 0L + val passes = satelliteRepo.passes.value + val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime } + val currentPass = pass ?: passes.firstOrNull() + currentPass?.let { satPass -> + _uiState.update { it.copy(currentPass = satPass) } + val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum) + while (isActive) { + val timeNow = System.currentTimeMillis() + val pos = satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow) + if (satPass.aosTime > timeNow) { + val time = satPass.aosTime.minus(timeNow).toTimerString() + _uiState.update { it.copy(currentTime = time, isCurrentTimeAos = true) } + } else { + val time = satPass.losTime.minus(timeNow).toTimerString() + _uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false) } + } + sendPassData(satPass, pos, satPass.orbitalObject) + sendPassDataBT(pos) + processRadios(transmitters, satPass.orbitalObject, timeNow) + delay(1000) } } } @@ -77,56 +116,29 @@ class RadarViewModel( super.onCleared() } - fun getPass() = flow { - val catNum = savedStateHandle.get("catNum") ?: 0 - val aosTime = savedStateHandle.get("aosTime") ?: 0L - val passes = satelliteRepo.passes.value - val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime } - val currentPass = pass ?: passes.firstOrNull() - currentPass?.let { satPass -> - emit(satPass) - val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum) - viewModelScope.launch { - while (isActive) { - val timeNow = System.currentTimeMillis() - val satPos = - satelliteRepo.getPosition(satPass.orbitalObject, stationPos, timeNow) - sendPassData(satPass, satPos, satPass.orbitalObject) - sendPassDataBT(satPos) - processRadios(transmitters, satPass.orbitalObject, timeNow) - delay(1000) - } - } + private fun handleAction(action: RadarAction) { + when (action) { + is RadarAction.AddToCalendar -> addToCalendar(action.name, action.aosTime, action.losTime) } } -// fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState -// -// fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState - - private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float { - val latitude = geoPos.latitude.toFloat() - val longitude = geoPos.longitude.toFloat() - return GeomagneticField(latitude, longitude, geoPos.altitude.toFloat(), time).declination - } - - private fun sendPassData(orbitalPass: OrbitalPass, orbitalPos: OrbitalPos, orbitalObject: OrbitalObject) { - viewModelScope.launch { - var track: List = emptyList() - if (!orbitalPass.isDeepSpace) { - track = satelliteRepo.getTrack( - orbitalObject, stationPos, orbitalPass.aosTime, orbitalPass.losTime - ) - } - if (settingsRepo.getRotatorState()) { - val server = settingsRepo.getRotatorAddress() - val port = settingsRepo.getRotatorPort().toInt() - val azimuth = orbitalPos.azimuth.toDegrees().round(2) - val elevation = orbitalPos.elevation.toDegrees().round(2) - networkReporter.reportRotation(server, port, azimuth, elevation) - } - radarData.value = RadarData(orbitalPass, orbitalPos, track) + private suspend fun sendPassData(orbitalPass: OrbitalPass, orbitalPos: OrbitalPos, orbitalObject: OrbitalObject) { + var track: List = emptyList() + if (!orbitalPass.isDeepSpace) { + track = satelliteRepo.getTrack( + orbitalObject, stationPos, orbitalPass.aosTime, orbitalPass.losTime + ) } + _uiState.update { it.copy(orbitalPos = orbitalPos, satTrack = track) } +// viewModelScope.launch { +// if (settingsRepo.getRotatorState()) { +// val server = settingsRepo.getRotatorAddress() +// val port = settingsRepo.getRotatorPort().toInt() +// val azimuth = orbitalPos.azimuth.toDegrees().round(2) +// val elevation = orbitalPos.elevation.toDegrees().round(2) +// networkReporter.reportRotation(server, port, azimuth, elevation) +// } +// } } private fun sendPassDataBT(orbitalPos: OrbitalPos) { @@ -139,19 +151,16 @@ class RadarViewModel( val elevation = orbitalPos.elevation.toDegrees().round(0).toInt() bluetoothReporter.reportRotation(format, azimuth, elevation) } else if (!bluetoothReporter.isConnecting()) { - Log.i("BTReporter", "BTReporter: Attempting to connect...") +// Log.i("BTReporter", "BTReporter: Attempting to connect...") bluetoothReporter.connectBTDevice(btDevice) } } } } - private fun processRadios(radios: List, orbitalObject: OrbitalObject, time: Long) { - viewModelScope.launch { - delay(125) - val list = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time) - transmitters.value = list - } + private suspend fun processRadios(radios: List, orbitalObject: OrbitalObject, time: Long) { + val transmitters = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time) + _uiState.update { it.copy(transmitters = transmitters) } } companion object { @@ -165,7 +174,8 @@ class RadarViewModel( container.provideNetworkReporter(), container.satelliteRepo, container.settingsRepo, - container.provideSensorsRepo() + container.provideSensorsRepo(), + container.provideAddToCalendar() ) } } 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 44903f9f..7775e781 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 @@ -178,7 +178,7 @@ private fun MiddleBar( private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifier = Modifier) { val typesText = if (types.isEmpty()) "All" else types.joinToString(", ") ElevatedCard(modifier = modifier) { - Row(horizontalArrangement = Arrangement.Start, + Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .height(48.dp) @@ -194,9 +194,8 @@ private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifie text = "Types: $typesText", fontSize = 16.sp, fontWeight = FontWeight.Medium, - modifier = modifier - .basicMarquee(iterations = Int.MAX_VALUE, spacing = MarqueeSpacing(0.dp)) - .padding(start = 12.dp, end = 6.dp, bottom = 2.dp) + modifier = Modifier + .basicMarquee(iterations = Int.MAX_VALUE, spacing = MarqueeSpacing(16.dp)) ) } } diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 00000000..805a9e6a --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SensorsRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SensorsRepo.kt index df56eb82..74d2eed3 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SensorsRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SensorsRepo.kt @@ -17,15 +17,17 @@ */ package com.rtbishop.look4sat.data.repository +import android.hardware.GeomagneticField import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager +import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.RAD2DEG import com.rtbishop.look4sat.domain.repository.ISensorsRepo -import kotlin.math.round import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlin.math.round class SensorsRepo(private val sensorManager: SensorManager, private val sensor: Sensor?) : SensorEventListener, ISensorsRepo { @@ -37,6 +39,12 @@ class SensorsRepo(private val sensorManager: SensorManager, private val sensor: override val orientation: StateFlow> = _orientation + override fun getMagDeclination(geoPos: GeoPos, time: Long): Float { + val latitude = geoPos.latitude.toFloat() + val longitude = geoPos.longitude.toFloat() + return GeomagneticField(latitude, longitude, geoPos.altitude.toFloat(), time).declination + } + override fun enableSensor() { sensor?.let { sensorManager.registerListener(this, it, 8000) } } diff --git a/data/src/main/java/com/rtbishop/look4sat/data/usecase/AddToCalendar.kt b/data/src/main/java/com/rtbishop/look4sat/data/usecase/AddToCalendar.kt new file mode 100644 index 00000000..1a05fdb7 --- /dev/null +++ b/data/src/main/java/com/rtbishop/look4sat/data/usecase/AddToCalendar.kt @@ -0,0 +1,24 @@ +package com.rtbishop.look4sat.data.usecase + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.provider.CalendarContract +import com.rtbishop.look4sat.domain.usecase.IAddToCalendar + +class AddToCalendar(private val context: Context) : IAddToCalendar { + override fun invoke(name: String, aosTime: Long, losTime: Long) { + val intent = Intent(Intent.ACTION_INSERT).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setData(CalendarContract.Events.CONTENT_URI) + putExtra(CalendarContract.Events.TITLE, name) + putExtra(CalendarContract.Events.DESCRIPTION, "Look4Sat Pass") + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, aosTime) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, losTime) + } + try { + context.startActivity(intent) + } catch (_: ActivityNotFoundException) { + } + } +} diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISensorsRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISensorsRepo.kt index 5167630f..aaff74a6 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISensorsRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISensorsRepo.kt @@ -1,9 +1,11 @@ package com.rtbishop.look4sat.domain.repository +import com.rtbishop.look4sat.domain.predict.GeoPos import kotlinx.coroutines.flow.StateFlow interface ISensorsRepo { val orientation: StateFlow> + fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float fun enableSensor() fun disableSensor() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IAddToCalendar.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IAddToCalendar.kt new file mode 100644 index 00000000..a73c391d --- /dev/null +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IAddToCalendar.kt @@ -0,0 +1,5 @@ +package com.rtbishop.look4sat.domain.usecase + +interface IAddToCalendar { + operator fun invoke(name: String, aosTime: Long, losTime: Long) +} diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/Extensions.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/Extensions.kt index 0bd63eac..f68cadbd 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/utility/Extensions.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/utility/Extensions.kt @@ -17,6 +17,7 @@ */ package com.rtbishop.look4sat.domain.utility +import java.util.Locale import java.util.concurrent.TimeUnit fun Long.toTimerString(): String { @@ -24,7 +25,7 @@ fun Long.toTimerString(): String { val hours = TimeUnit.MILLISECONDS.toHours(this) val minutes = TimeUnit.MILLISECONDS.toMinutes(this) % 60 val seconds = TimeUnit.MILLISECONDS.toSeconds(this) % 60 - return String.format(format, hours, minutes, seconds) + return String.format(Locale.ENGLISH, format, hours, minutes, seconds) } fun Float.round(decimals: Int): Float {