Added multiple fixes to RadarScreen and components

develop
Arty Bishop 2024-12-17 09:35:33 +00:00
rodzic be82b19b37
commit 9f771472c2
16 zmienionych plików z 304 dodań i 184 usunięć

Wyświetl plik

@ -16,6 +16,7 @@ import com.rtbishop.look4sat.data.repository.SensorsRepo
import com.rtbishop.look4sat.data.repository.SettingsRepo import com.rtbishop.look4sat.data.repository.SettingsRepo
import com.rtbishop.look4sat.data.source.LocalSource import com.rtbishop.look4sat.data.source.LocalSource
import com.rtbishop.look4sat.data.source.RemoteSource 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.IDatabaseRepo
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISelectionRepo 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.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.source.ILocalSource import com.rtbishop.look4sat.domain.source.ILocalSource
import com.rtbishop.look4sat.domain.source.IRemoteSource import com.rtbishop.look4sat.domain.source.IRemoteSource
import com.rtbishop.look4sat.domain.usecase.IAddToCalendar
import com.rtbishop.look4sat.domain.utility.DataParser import com.rtbishop.look4sat.domain.utility.DataParser
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -41,6 +43,10 @@ class MainContainer(private val context: Context) {
val satelliteRepo = provideSatelliteRepo() val satelliteRepo = provideSatelliteRepo()
val databaseRepo = provideDatabaseRepo() val databaseRepo = provideDatabaseRepo()
fun provideAddToCalendar(): IAddToCalendar {
return AddToCalendar(context)
}
fun provideBluetoothReporter(): BluetoothReporter { fun provideBluetoothReporter(): BluetoothReporter {
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO)) return BluetoothReporter(manager, CoroutineScope(Dispatchers.IO))

Wyświetl plik

@ -16,7 +16,6 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.info.infoDestination import com.rtbishop.look4sat.presentation.info.infoDestination
import com.rtbishop.look4sat.presentation.map.mapDestination import com.rtbishop.look4sat.presentation.map.mapDestination
@ -42,14 +41,9 @@ fun MainScreen() {
val routeWithParams = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}" val routeWithParams = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
outerNavController.navigate(routeWithParams) 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) { NavHost(navController = outerNavController, startDestination = Screen.Main.route) {
mainDestination(navigateToRadar) mainDestination(navigateToRadar)
radarDestination(radarRoute, radarArgs) radarDestination { outerNavController.navigateUp() }
} }
} }

Wyświetl plik

@ -80,14 +80,14 @@ fun RowScope.TimerBar(timeString: String, isTimeAos: Boolean) {
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text(text = "AOS", fontSize = 16.sp, color = aosColor) Text(text = "AOS", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = aosColor)
Text( Text(
text = timeString, text = timeString,
fontSize = 32.sp, fontSize = 32.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary color = MaterialTheme.colorScheme.primary
) )
Text(text = "LOS", fontSize = 16.sp, color = losColor) Text(text = "LOS", fontSize = 16.sp, fontWeight = FontWeight.Medium, color = losColor)
} }
} }
} }

Wyświetl plik

@ -34,6 +34,11 @@ import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.OrbitalObject import com.rtbishop.look4sat.domain.predict.OrbitalObject
import com.rtbishop.look4sat.domain.predict.OrbitalPos import com.rtbishop.look4sat.domain.predict.OrbitalPos
import com.rtbishop.look4sat.presentation.Screen 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.tileprovider.tilesource.XYTileSource
import org.osmdroid.util.GeoPoint import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController import org.osmdroid.views.CustomZoomButtonsController
@ -78,6 +83,12 @@ private fun MapScreen() {
val footprint = viewModel.footprint.collectAsState(initial = null) val footprint = viewModel.footprint.collectAsState(initial = null)
val positionClick = { orbitalObject: OrbitalObject -> viewModel.selectSatellite(orbitalObject) } val positionClick = { orbitalObject: OrbitalObject -> viewModel.selectSatellite(orbitalObject) }
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { 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()) { ElevatedCard(modifier = Modifier.fillMaxSize()) {
MapView(modifier = Modifier.fillMaxSize(), isLightTheme = viewModel.stateOfLightTheme) { mapView -> MapView(modifier = Modifier.fillMaxSize(), isLightTheme = viewModel.stateOfLightTheme) { mapView ->
stationPos.value?.let { setStationPosition(it, mapView) } stationPos.value?.let { setStationPosition(it, mapView) }

Wyświetl plik

@ -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 <https://www.gnu.org/licenses/>.
*/
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<OrbitalPos> = emptyList()
)

Wyświetl plik

@ -1,9 +1,13 @@
package com.rtbishop.look4sat.presentation.radar package com.rtbishop.look4sat.presentation.radar
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
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.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.padding import androidx.compose.foundation.layout.padding
@ -16,57 +20,77 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign 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.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.NamedNavArgument
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.SatRadio 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<NamedNavArgument>) { fun NavGraphBuilder.radarDestination(navigateBack: () -> Unit) {
composable(radarRoute, radarArgs) { RadarScreen() } 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 @Composable
private fun RadarScreen() { private fun RadarScreen(uiState: RadarState, navigateBack: () -> Unit) {
val viewModel = viewModel(RadarViewModel::class.java, factory = RadarViewModel.Factory) val addToCalendar: () -> Unit = {
val currentPass = viewModel.getPass().collectAsState(null).value uiState.currentPass?.let { pass ->
val id = currentPass?.catNum ?: 99999 uiState.sendAction(RadarAction.AddToCalendar(pass.name, pass.aosTime, pass.losTime))
val name = currentPass?.name ?: "Satellite" }
}
Scaffold { innerPadding -> Scaffold { innerPadding ->
val paddingMod = Modifier.padding(innerPadding)
Column( Column(
modifier = Modifier.padding(innerPadding), modifier = paddingMod.padding(6.dp),
verticalArrangement = Arrangement.spacedBy(6.dp) verticalArrangement = Arrangement.spacedBy(6.dp)
) { ) {
// TimerBarNew(id, name, "88:88:88", R.drawable.ic_notifications) {} TimerRow {
ElevatedCard( CardIcon(onClick = navigateBack, iconId = R.drawable.ic_back)
modifier = Modifier TimerBar(timeString = uiState.currentTime, isTimeAos = uiState.isCurrentTimeAos)
.fillMaxSize() CardIcon(onClick = addToCalendar, iconId = R.drawable.ic_calendar)
.weight(1f) }
) { NextPassRow(pass = uiState.currentPass ?: getDefaultPass())
viewModel.radarData.value?.let { data -> ElevatedCard(modifier = Modifier.aspectRatio(1f)) {
uiState.orbitalPos?.let { item ->
RadarViewCompose( RadarViewCompose(
item = data.orbitalPos, item = item,
items = data.satTrack, items = uiState.satTrack,
azimElev = viewModel.orientation.value azimElev = uiState.orientationValues,
shouldShowSweep = uiState.shouldShowSweep,
shouldUseCompass = uiState.shouldUseCompass
) )
} }
} }
ElevatedCard( ElevatedCard(modifier = Modifier.fillMaxSize()) {
modifier = Modifier TransmittersList(transmitters = uiState.transmitters)
.fillMaxSize()
.weight(1f)
) {
TransmittersList(transmitters = viewModel.transmitters.value)
} }
} }
} }
@ -81,6 +105,16 @@ private fun TransmittersList(transmitters: List<SatRadio>) {
} }
} }
@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 @Composable
private fun TransmitterItem(radio: SatRadio) { private fun TransmitterItem(radio: SatRadio) {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
@ -96,55 +130,80 @@ private fun TransmitterItem(radio: SatRadio) {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_arrow), painter = painterResource(id = R.drawable.ic_back),
contentDescription = null, modifier = Modifier.rotate(180f) tint = Color.Green,
contentDescription = null, modifier = Modifier.rotate(-90f)
) )
Text( Text(
text = radio.info, text = if (radio.isInverted) "INVERTED: ${radio.info} " else "${radio.info} ",
modifier = Modifier.weight(1f), textAlign = TextAlign.Center,
textAlign = TextAlign.Center modifier = Modifier
.basicMarquee(
iterations = Int.MAX_VALUE,
spacing = MarqueeSpacing(0.dp)
)
.weight(1f)
) )
Icon( Icon(
painter = painterResource(id = R.drawable.ic_arrow), painter = painterResource(id = R.drawable.ic_back),
contentDescription = null tint = Color.Red,
) contentDescription = null, modifier = Modifier.rotate(90f)
}
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
) )
} }
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 //private val divider = 1000000f
//radioDownlink.text = String.format(Locale.ENGLISH, link, downlink / divider) //radioDownlink.text = String.format(Locale.ENGLISH, link, downlink / divider)
//radioUplink.text = String.format(Locale.ENGLISH, link, uplink / divider) //radioUplink.text = String.format(Locale.ENGLISH, link, uplink / divider)

Wyświetl plik

@ -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<Float, Float>,
val orbitalPos: OrbitalPos?,
val satTrack: List<OrbitalPos>,
val shouldShowSweep: Boolean,
val shouldUseCompass: Boolean,
val transmitters: List<SatRadio>,
val sendAction: (RadarAction) -> Unit
)
sealed class RadarAction {
data class AddToCalendar(val name: String, val aosTime: Long, val losTime: Long) : RadarAction()
}

Wyświetl plik

@ -1,6 +1,5 @@
package com.rtbishop.look4sat.presentation.radar package com.rtbishop.look4sat.presentation.radar
import android.media.AudioAttributes
import android.media.SoundPool import android.media.SoundPool
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.infiniteRepeatable
@ -10,8 +9,6 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf 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.drawText
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.sp 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.OrbitalPos
import com.rtbishop.look4sat.domain.predict.PI_2 import com.rtbishop.look4sat.domain.predict.PI_2
import com.rtbishop.look4sat.domain.utility.toRadians import com.rtbishop.look4sat.domain.utility.toRadians
import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.sqrt
@Composable @Composable
fun RadarViewCompose(item: OrbitalPos, items: List<OrbitalPos>, azimElev: Pair<Float, Float>) { fun RadarViewCompose(
item: OrbitalPos,
items: List<OrbitalPos>,
azimElev: Pair<Float, Float>,
shouldShowSweep: Boolean,
shouldUseCompass: Boolean
) {
val context = LocalContext.current val context = LocalContext.current
val view = LocalView.current val view = LocalView.current
val radarColor = MaterialTheme.colorScheme.secondary val radarColor = MaterialTheme.colorScheme.secondary
@ -138,8 +135,8 @@ fun RadarViewCompose(item: OrbitalPos, items: List<OrbitalPos>, azimElev: Pair<F
trackEffect.value = createTrackEffect(trackPath.value) trackEffect.value = createTrackEffect(trackPath.value)
trackCreated.value = true trackCreated.value = true
} }
rotate(-azimElev.first) { rotate(if (shouldUseCompass) -azimElev.first else 0f) {
drawSweep(center, sweepDegrees.floatValue, radius, trackColor) if (shouldShowSweep) drawSweep(center, sweepDegrees.floatValue, radius, trackColor)
drawRadar(radius, radarColor, strokeWidth, 3) drawRadar(radius, radarColor, strokeWidth, 3)
drawInfo(radius, trackColor, measurer, 3) drawInfo(radius, trackColor, measurer, 3)
translate(center.x, center.y) { translate(center.x, center.y) {

Wyświetl plik

@ -17,9 +17,6 @@
*/ */
package com.rtbishop.look4sat.presentation.radar package com.rtbishop.look4sat.presentation.radar
import android.hardware.GeomagneticField
import android.util.Log
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -31,17 +28,20 @@ import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.data.framework.BluetoothReporter import com.rtbishop.look4sat.data.framework.BluetoothReporter
import com.rtbishop.look4sat.data.framework.NetworkReporter import com.rtbishop.look4sat.data.framework.NetworkReporter
import com.rtbishop.look4sat.domain.model.SatRadio import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.OrbitalObject import com.rtbishop.look4sat.domain.predict.OrbitalObject
import com.rtbishop.look4sat.domain.predict.OrbitalPass import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.domain.predict.OrbitalPos import com.rtbishop.look4sat.domain.predict.OrbitalPos
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
import com.rtbishop.look4sat.domain.repository.ISensorsRepo import com.rtbishop.look4sat.domain.repository.ISensorsRepo
import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.repository.ISettingsRepo
import com.rtbishop.look4sat.domain.usecase.IAddToCalendar
import com.rtbishop.look4sat.domain.utility.round import com.rtbishop.look4sat.domain.utility.round
import com.rtbishop.look4sat.domain.utility.toDegrees import com.rtbishop.look4sat.domain.utility.toDegrees
import com.rtbishop.look4sat.domain.utility.toTimerString
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow 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
@ -51,22 +51,61 @@ class RadarViewModel(
private val networkReporter: NetworkReporter, private val networkReporter: NetworkReporter,
private val satelliteRepo: ISatelliteRepo, private val satelliteRepo: ISatelliteRepo,
private val settingsRepo: ISettingsRepo, private val settingsRepo: ISettingsRepo,
private val sensorsRepo: ISensorsRepo private val sensorsRepo: ISensorsRepo,
private val addToCalendar: IAddToCalendar
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow(
RadarState(
currentPass = null,
currentTime = "00:00:00",
isCurrentTimeAos = true,
orientationValues = sensorsRepo.orientation.value,
orbitalPos = null,
satTrack = emptyList(),
shouldShowSweep = settingsRepo.otherSettings.value.stateOfSweep,
shouldUseCompass = settingsRepo.otherSettings.value.stateOfSensors,
transmitters = emptyList(),
sendAction = ::handleAction,
)
)
private val stationPos = settingsRepo.stationPosition.value private val stationPos = settingsRepo.stationPosition.value
private val magDeclination = getMagDeclination(stationPos) private val magDeclination = sensorsRepo.getMagDeclination(stationPos)
val stateOfLightTheme = settingsRepo.otherSettings.value.stateOfLightTheme val uiState: StateFlow<RadarState> = _uiState
val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
val radarData = mutableStateOf<RadarData?>(null)
val orientation = mutableStateOf(sensorsRepo.orientation.value)
init { init {
if (settingsRepo.otherSettings.value.stateOfSensors) { if (settingsRepo.otherSettings.value.stateOfSensors) {
viewModelScope.launch { viewModelScope.launch {
sensorsRepo.enableSensor() sensorsRepo.enableSensor()
sensorsRepo.orientation.collect { data -> 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<Int>("catNum") ?: 0
val aosTime = savedStateHandle.get<Long>("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() super.onCleared()
} }
fun getPass() = flow { private fun handleAction(action: RadarAction) {
val catNum = savedStateHandle.get<Int>("catNum") ?: 0 when (action) {
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L is RadarAction.AddToCalendar -> addToCalendar(action.name, action.aosTime, action.losTime)
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)
}
}
} }
} }
// fun getUseCompass(): Boolean = settingsRepo.otherSettings.value.sensorState private suspend fun sendPassData(orbitalPass: OrbitalPass, orbitalPos: OrbitalPos, orbitalObject: OrbitalObject) {
// var track: List<OrbitalPos> = emptyList()
// fun getShowSweep(): Boolean = settingsRepo.otherSettings.value.sweepState if (!orbitalPass.isDeepSpace) {
track = satelliteRepo.getTrack(
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float { orbitalObject, stationPos, orbitalPass.aosTime, orbitalPass.losTime
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<OrbitalPos> = 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)
} }
_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) { private fun sendPassDataBT(orbitalPos: OrbitalPos) {
@ -139,19 +151,16 @@ class RadarViewModel(
val elevation = orbitalPos.elevation.toDegrees().round(0).toInt() val elevation = orbitalPos.elevation.toDegrees().round(0).toInt()
bluetoothReporter.reportRotation(format, azimuth, elevation) bluetoothReporter.reportRotation(format, azimuth, elevation)
} else if (!bluetoothReporter.isConnecting()) { } else if (!bluetoothReporter.isConnecting()) {
Log.i("BTReporter", "BTReporter: Attempting to connect...") // Log.i("BTReporter", "BTReporter: Attempting to connect...")
bluetoothReporter.connectBTDevice(btDevice) bluetoothReporter.connectBTDevice(btDevice)
} }
} }
} }
} }
private fun processRadios(radios: List<SatRadio>, orbitalObject: OrbitalObject, time: Long) { private suspend fun processRadios(radios: List<SatRadio>, orbitalObject: OrbitalObject, time: Long) {
viewModelScope.launch { val transmitters = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time)
delay(125) _uiState.update { it.copy(transmitters = transmitters) }
val list = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time)
transmitters.value = list
}
} }
companion object { companion object {
@ -165,7 +174,8 @@ class RadarViewModel(
container.provideNetworkReporter(), container.provideNetworkReporter(),
container.satelliteRepo, container.satelliteRepo,
container.settingsRepo, container.settingsRepo,
container.provideSensorsRepo() container.provideSensorsRepo(),
container.provideAddToCalendar()
) )
} }
} }

Wyświetl plik

@ -178,7 +178,7 @@ private fun MiddleBar(
private fun TypeCard(types: List<String>, onClick: () -> Unit, modifier: Modifier = Modifier) { private fun TypeCard(types: List<String>, onClick: () -> Unit, modifier: Modifier = Modifier) {
val typesText = if (types.isEmpty()) "All" else types.joinToString(", ") val typesText = if (types.isEmpty()) "All" else types.joinToString(", ")
ElevatedCard(modifier = modifier) { ElevatedCard(modifier = modifier) {
Row(horizontalArrangement = Arrangement.Start, Row(horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.height(48.dp) .height(48.dp)
@ -194,9 +194,8 @@ private fun TypeCard(types: List<String>, onClick: () -> Unit, modifier: Modifie
text = "Types: $typesText", text = "Types: $typesText",
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
modifier = modifier modifier = Modifier
.basicMarquee(iterations = Int.MAX_VALUE, spacing = MarqueeSpacing(0.dp)) .basicMarquee(iterations = Int.MAX_VALUE, spacing = MarqueeSpacing(16.dp))
.padding(start = 12.dp, end = 6.dp, bottom = 2.dp)
) )
} }
} }

Wyświetl plik

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>

Wyświetl plik

@ -17,15 +17,17 @@
*/ */
package com.rtbishop.look4sat.data.repository package com.rtbishop.look4sat.data.repository
import android.hardware.GeomagneticField
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener import android.hardware.SensorEventListener
import android.hardware.SensorManager import android.hardware.SensorManager
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.RAD2DEG import com.rtbishop.look4sat.domain.predict.RAD2DEG
import com.rtbishop.look4sat.domain.repository.ISensorsRepo import com.rtbishop.look4sat.domain.repository.ISensorsRepo
import kotlin.math.round
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlin.math.round
class SensorsRepo(private val sensorManager: SensorManager, private val sensor: Sensor?) : class SensorsRepo(private val sensorManager: SensorManager, private val sensor: Sensor?) :
SensorEventListener, ISensorsRepo { SensorEventListener, ISensorsRepo {
@ -37,6 +39,12 @@ class SensorsRepo(private val sensorManager: SensorManager, private val sensor:
override val orientation: StateFlow<Pair<Float, Float>> = _orientation override val orientation: StateFlow<Pair<Float, Float>> = _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() { override fun enableSensor() {
sensor?.let { sensorManager.registerListener(this, it, 8000) } sensor?.let { sensorManager.registerListener(this, it, 8000) }
} }

Wyświetl plik

@ -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) {
}
}
}

Wyświetl plik

@ -1,9 +1,11 @@
package com.rtbishop.look4sat.domain.repository package com.rtbishop.look4sat.domain.repository
import com.rtbishop.look4sat.domain.predict.GeoPos
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface ISensorsRepo { interface ISensorsRepo {
val orientation: StateFlow<Pair<Float, Float>> val orientation: StateFlow<Pair<Float, Float>>
fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float
fun enableSensor() fun enableSensor()
fun disableSensor() fun disableSensor()
} }

Wyświetl plik

@ -0,0 +1,5 @@
package com.rtbishop.look4sat.domain.usecase
interface IAddToCalendar {
operator fun invoke(name: String, aosTime: Long, losTime: Long)
}

Wyświetl plik

@ -17,6 +17,7 @@
*/ */
package com.rtbishop.look4sat.domain.utility package com.rtbishop.look4sat.domain.utility
import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
fun Long.toTimerString(): String { fun Long.toTimerString(): String {
@ -24,7 +25,7 @@ fun Long.toTimerString(): String {
val hours = TimeUnit.MILLISECONDS.toHours(this) val hours = TimeUnit.MILLISECONDS.toHours(this)
val minutes = TimeUnit.MILLISECONDS.toMinutes(this) % 60 val minutes = TimeUnit.MILLISECONDS.toMinutes(this) % 60
val seconds = TimeUnit.MILLISECONDS.toSeconds(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 { fun Float.round(decimals: Int): Float {