kopia lustrzana https://github.com/rt-bishop/Look4Sat
Added multiple fixes to RadarScreen and components
rodzic
33bcc138f7
commit
1657fad715
|
@ -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))
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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()
|
||||
)
|
|
@ -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<NamedNavArgument>) {
|
||||
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<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
|
||||
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)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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<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 view = LocalView.current
|
||||
val radarColor = MaterialTheme.colorScheme.secondary
|
||||
|
@ -138,8 +135,8 @@ fun RadarViewCompose(item: OrbitalPos, items: List<OrbitalPos>, azimElev: Pair<F
|
|||
trackEffect.value = createTrackEffect(trackPath.value)
|
||||
trackCreated.value = true
|
||||
}
|
||||
rotate(-azimElev.first) {
|
||||
drawSweep(center, sweepDegrees.floatValue, radius, trackColor)
|
||||
rotate(if (shouldUseCompass) -azimElev.first else 0f) {
|
||||
if (shouldShowSweep) drawSweep(center, sweepDegrees.floatValue, radius, trackColor)
|
||||
drawRadar(radius, radarColor, strokeWidth, 3)
|
||||
drawInfo(radius, trackColor, measurer, 3)
|
||||
translate(center.x, center.y) {
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
*/
|
||||
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.ViewModel
|
||||
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.NetworkReporter
|
||||
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.OrbitalPass
|
||||
import com.rtbishop.look4sat.domain.predict.OrbitalPos
|
||||
import com.rtbishop.look4sat.domain.repository.ISatelliteRepo
|
||||
import com.rtbishop.look4sat.domain.repository.ISensorsRepo
|
||||
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.toDegrees
|
||||
import com.rtbishop.look4sat.domain.utility.toTimerString
|
||||
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.launch
|
||||
|
||||
|
@ -51,22 +51,61 @@ class RadarViewModel(
|
|||
private val networkReporter: NetworkReporter,
|
||||
private val satelliteRepo: ISatelliteRepo,
|
||||
private val settingsRepo: ISettingsRepo,
|
||||
private val sensorsRepo: ISensorsRepo
|
||||
private val sensorsRepo: ISensorsRepo,
|
||||
private val addToCalendar: IAddToCalendar
|
||||
) : 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 magDeclination = getMagDeclination(stationPos)
|
||||
val stateOfLightTheme = settingsRepo.otherSettings.value.stateOfLightTheme
|
||||
val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
|
||||
val radarData = mutableStateOf<RadarData?>(null)
|
||||
val orientation = mutableStateOf(sensorsRepo.orientation.value)
|
||||
private val magDeclination = sensorsRepo.getMagDeclination(stationPos)
|
||||
val uiState: StateFlow<RadarState> = _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<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()
|
||||
}
|
||||
|
||||
fun getPass() = flow {
|
||||
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 ->
|
||||
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<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)
|
||||
private suspend fun sendPassData(orbitalPass: OrbitalPass, orbitalPos: OrbitalPos, orbitalObject: OrbitalObject) {
|
||||
var track: List<OrbitalPos> = 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<SatRadio>, 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<SatRadio>, 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ private fun MiddleBar(
|
|||
private fun TypeCard(types: List<String>, 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<String>, 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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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<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() {
|
||||
sensor?.let { sensorManager.registerListener(this, it, 8000) }
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Pair<Float, Float>>
|
||||
fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float
|
||||
fun enableSensor()
|
||||
fun disableSensor()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package com.rtbishop.look4sat.domain.usecase
|
||||
|
||||
interface IAddToCalendar {
|
||||
operator fun invoke(name: String, aosTime: Long, losTime: Long)
|
||||
}
|
|
@ -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 {
|
||||
|
|
Ładowanie…
Reference in New Issue