Even more restructuring and code cleanup

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:40 +00:00
1 parent a807cfd8f6
commit f8e7bcdf39
12 files changed
+222 -217

No files matched your search

@@ -8,21 +8,21 @@ import android.location.LocationManager
import androidx.room.Room import androidx.room.Room
import com.rtbishop.look4sat.data.DataParser import com.rtbishop.look4sat.data.DataParser
import com.rtbishop.look4sat.data.DatabaseRepo import com.rtbishop.look4sat.data.DatabaseRepo
import com.rtbishop.look4sat.data.ISensorSource
import com.rtbishop.look4sat.data.SatelliteRepo import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.data.SelectionRepo import com.rtbishop.look4sat.data.SelectionRepo
import com.rtbishop.look4sat.domain.IDatabaseRepo import com.rtbishop.look4sat.domain.IDatabaseRepo
import com.rtbishop.look4sat.domain.ISatelliteRepo import com.rtbishop.look4sat.domain.ISatelliteRepo
import com.rtbishop.look4sat.domain.ISelectionRepo import com.rtbishop.look4sat.domain.ISelectionRepo
import com.rtbishop.look4sat.domain.ISensorsRepo
import com.rtbishop.look4sat.domain.ISettingsRepo import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.framework.BluetoothReporter import com.rtbishop.look4sat.framework.BluetoothReporter
import com.rtbishop.look4sat.framework.NetworkReporter import com.rtbishop.look4sat.framework.NetworkReporter
import com.rtbishop.look4sat.framework.SensorsRepo
import com.rtbishop.look4sat.framework.SettingsRepo import com.rtbishop.look4sat.framework.SettingsRepo
import com.rtbishop.look4sat.framework.data.DataSource import com.rtbishop.look4sat.framework.data.DataSource
import com.rtbishop.look4sat.framework.data.LocalStorage import com.rtbishop.look4sat.framework.data.LocalStorage
import com.rtbishop.look4sat.framework.data.MIGRATION_1_2 import com.rtbishop.look4sat.framework.data.MIGRATION_1_2
import com.rtbishop.look4sat.framework.data.MainDatabase import com.rtbishop.look4sat.framework.data.MainDatabase
import com.rtbishop.look4sat.framework.data.SensorSource
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -40,7 +40,6 @@ class MainContainer(private val context: Context) {
val databaseRepo = provideDatabaseRepo() val databaseRepo = provideDatabaseRepo()
val satelliteRepo = provideSatelliteRepo() val satelliteRepo = provideSatelliteRepo()
val selectionRepo = provideSelectionRepo() val selectionRepo = provideSelectionRepo()
val sensorsRepo = provideSensorsRepo()
fun provideBluetoothReporter(): BluetoothReporter { fun provideBluetoothReporter(): BluetoothReporter {
val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@@ -51,6 +50,12 @@ class MainContainer(private val context: Context) {
return NetworkReporter(CoroutineScope(Dispatchers.IO)) return NetworkReporter(CoroutineScope(Dispatchers.IO))
} }
fun provideSensorSource(): ISensorSource {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
return SensorSource(sensorManager, sensor)
}
private fun provideDatabaseRepo(): IDatabaseRepo { private fun provideDatabaseRepo(): IDatabaseRepo {
val dataParser = DataParser(Dispatchers.Default) val dataParser = DataParser(Dispatchers.Default)
val dataSource = DataSource(context.contentResolver, Dispatchers.IO) val dataSource = DataSource(context.contentResolver, Dispatchers.IO)
@@ -58,19 +63,13 @@ class MainContainer(private val context: Context) {
} }
private fun provideSatelliteRepo(): ISatelliteRepo { private fun provideSatelliteRepo(): ISatelliteRepo {
return SatelliteRepo(Dispatchers.Default, localStorage) return SatelliteRepo(Dispatchers.Default, localStorage, settingsRepo)
} }
private fun provideSelectionRepo(): ISelectionRepo { private fun provideSelectionRepo(): ISelectionRepo {
return SelectionRepo(Dispatchers.Default, localStorage, settingsRepo) return SelectionRepo(Dispatchers.Default, localStorage, settingsRepo)
} }
private fun provideSensorsRepo(): ISensorsRepo {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
return SensorsRepo(sensorManager, sensor)
}
private fun provideSettingsRepo(): ISettingsRepo { private fun provideSettingsRepo(): ISettingsRepo {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE) val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
@@ -27,6 +27,7 @@ import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.model.DatabaseState import com.rtbishop.look4sat.model.DatabaseState
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.OtherSettings import com.rtbishop.look4sat.model.OtherSettings
import com.rtbishop.look4sat.model.PassesSettings
import com.rtbishop.look4sat.utility.QthConverter import com.rtbishop.look4sat.utility.QthConverter
import com.rtbishop.look4sat.utility.round import com.rtbishop.look4sat.utility.round
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -99,15 +100,6 @@ class SettingsRepo(
return true return true
} }
private fun loadStationPosition(): GeoPos {
val latitude = (preferences.getString(keyLatitude, null) ?: "0.0").toDouble()
val longitude = (preferences.getString(keyLongitude, null) ?: "0.0").toDouble()
val altitude = (preferences.getString(keyAltitude, null) ?: "0.0").toDouble()
val qthLocator = preferences.getString(keyLocator, null) ?: "null"
val timestamp = preferences.getLong(keyLocTimestamp, 0L)
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
}
private fun saveStationPosition(stationPos: GeoPos) = preferences.edit { private fun saveStationPosition(stationPos: GeoPos) = preferences.edit {
putString(keyLatitude, stationPos.latitude.toString()) putString(keyLatitude, stationPos.latitude.toString())
putString(keyLongitude, stationPos.longitude.toString()) putString(keyLongitude, stationPos.longitude.toString())
@@ -126,8 +118,51 @@ class SettingsRepo(
saveStationPosition(GeoPos(newLat, newLon, newAlt, locator, timestamp)) saveStationPosition(GeoPos(newLat, newLon, newAlt, locator, timestamp))
} }
private fun loadStationPosition(): GeoPos {
val latitude = (preferences.getString(keyLatitude, null) ?: "0.0").toDouble()
val longitude = (preferences.getString(keyLongitude, null) ?: "0.0").toDouble()
val altitude = (preferences.getString(keyAltitude, null) ?: "0.0").toDouble()
val qthLocator = preferences.getString(keyLocator, null) ?: "null"
val timestamp = preferences.getLong(keyLocTimestamp, 0L)
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
}
//endregion //endregion
//region # Database update settings
private val _databaseState = MutableStateFlow(loadDatabaseState())
override val databaseState: StateFlow<DatabaseState> = _databaseState
override fun saveDatabaseState(state: DatabaseState) = preferences.edit {
putInt(keyDataEntries, state.entriesTotal)
putInt(keyDataRadios, state.radiosTotal)
putLong(keyDataTimestamp, state.timestamp)
_databaseState.value = state
}
override fun saveSatType(type: String, catnums: List<Int>) {
val stringList = catnums.map { catnum -> catnum.toString() }
preferences.edit { putStringSet("type$type", stringList.toSet()) }
}
override fun loadSatType(type: String): List<Int> {
val catnums =
preferences.getStringSet("type$type", emptySet())?.map { catnum -> catnum.toInt() }
return catnums ?: emptyList()
}
private fun loadDatabaseState(): DatabaseState {
val entriesTotal = preferences.getInt(keyDataEntries, 0)
val radiosTotal = preferences.getInt(keyDataRadios, 0)
val timestamp = preferences.getLong(keyDataTimestamp, 0L)
return DatabaseState(entriesTotal, radiosTotal, timestamp)
}
//endregion
//region # Entries selection settings
private val _satelliteSelection = MutableStateFlow(loadEntriesSelection()) private val _satelliteSelection = MutableStateFlow(loadEntriesSelection())
override val satelliteSelection: StateFlow<List<Int>> = _satelliteSelection override val satelliteSelection: StateFlow<List<Int>> = _satelliteSelection
@@ -141,49 +176,36 @@ class SettingsRepo(
return catNums?.map { catnum -> catnum.toInt() }?.sorted() ?: emptyList() return catNums?.map { catnum -> catnum.toInt() }?.sorted() ?: emptyList()
} }
override fun saveSatType(type: String, catnums: List<Int>) { //endregion
val stringList = catnums.map { catnum -> catnum.toString() }
preferences.edit { putStringSet("type$type", stringList.toSet()) } //region # Passes filter settings
private val _passesSettings = MutableStateFlow(loadPassesSettings())
override val passesSettings: StateFlow<PassesSettings> = _passesSettings
override fun savePassesSettings(settings: PassesSettings) = preferences.edit {
putInt(keyHoursAhead, settings.hoursAhead)
putDouble(keyMinElevation, settings.minElevation)
_passesSettings.value = settings
} }
override fun loadSatType(type: String): List<Int> { override fun saveModesSelection(modes: List<String>) {
val catnums = preferences.edit { putStringSet(keyModes, modes.toSet()) }
preferences.getStringSet("type$type", emptySet())?.map { catnum -> catnum.toInt() }
return catnums ?: emptyList()
} }
override fun getHoursAhead(): Int { override fun loadModesSelection(): List<String> {
return preferences.getInt(keyHoursAhead, 24) return preferences.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
} }
override fun setHoursAhead(hoursAhead: Int) { private fun loadPassesSettings(): PassesSettings {
preferences.edit { putInt(keyHoursAhead, hoursAhead) } val hoursAhead = preferences.getInt(keyHoursAhead, 24)
val minElevation = preferences.getDouble(keyMinElevation, 16.0)
return PassesSettings(hoursAhead, minElevation)
} }
override fun getMinElevation(): Double { //endregion
return preferences.getDouble(keyMinElevation, 16.0)
}
override fun setMinElevation(minElevation: Double) { //region # Other settings
preferences.edit { putDouble(keyMinElevation, minElevation) }
}
private val _databaseState = MutableStateFlow(loadDatabaseState())
override val databaseState: StateFlow<DatabaseState> = _databaseState
override fun saveDatabaseState(state: DatabaseState) = preferences.edit {
putInt(keyDataEntries, state.entriesTotal)
putInt(keyDataRadios, state.radiosTotal)
putLong(keyDataTimestamp, state.timestamp)
_databaseState.value = state
}
private fun loadDatabaseState(): DatabaseState {
val entriesTotal = preferences.getInt(keyDataEntries, 0)
val radiosTotal = preferences.getInt(keyDataRadios, 0)
val timestamp = preferences.getLong(keyDataTimestamp, 0L)
return DatabaseState(entriesTotal, radiosTotal, timestamp)
}
private val _otherSettings = MutableStateFlow(loadOtherSettings()) private val _otherSettings = MutableStateFlow(loadOtherSettings())
override val otherSettings: StateFlow<OtherSettings> = _otherSettings override val otherSettings: StateFlow<OtherSettings> = _otherSettings
@@ -216,13 +238,9 @@ class SettingsRepo(
return OtherSettings(utcState, updateState, sweepState, sensorState) return OtherSettings(utcState, updateState, sweepState, sensorState)
} }
override fun saveModesSelection(modes: List<String>) { //endregion
preferences.edit { putStringSet(keyModes, modes.toSet()) }
}
override fun loadModesSelection(): List<String> { //region # Undefined settings
return preferences.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
}
override fun getRotatorEnabled(): Boolean { override fun getRotatorEnabled(): Boolean {
return preferences.getBoolean(keyRotator, false) return preferences.getBoolean(keyRotator, false)
@@ -280,6 +298,8 @@ class SettingsRepo(
preferences.edit { putString(keyBTFormat, value) } preferences.edit { putString(keyBTFormat, value) }
} }
//endregion
private fun SharedPreferences.getDouble(key: String, default: Double): Double { private fun SharedPreferences.getDouble(key: String, default: Double): Double {
return Double.fromBits(getLong(key, default.toRawBits())) return Double.fromBits(getLong(key, default.toRawBits()))
} }
@@ -15,30 +15,30 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.rtbishop.look4sat.framework package com.rtbishop.look4sat.framework.data
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.ISensorsRepo import com.rtbishop.look4sat.data.ISensorSource
import com.rtbishop.look4sat.domain.RAD2DEG import com.rtbishop.look4sat.domain.RAD2DEG
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlin.math.round import kotlin.math.round
class SensorsRepo(private val sensorManager: SensorManager, private val sensor: Sensor?) : class SensorSource(private val sensorManager: SensorManager, private val sensor: Sensor?) :
SensorEventListener, ISensorsRepo { SensorEventListener, ISensorSource {
private val rotationMatrix = FloatArray(9)
private val orientationValues = FloatArray(3)
private var sensorAccuracy: Int = SensorManager.SENSOR_STATUS_UNRELIABLE
private val _orientation = MutableStateFlow(Pair(0f, 0f)) private val _orientation = MutableStateFlow(Pair(0f, 0f))
private val rotationMatrix = FloatArray(9)
private val orientationValues = FloatArray(3)
private var sensorAccuracy = SensorManager.SENSOR_STATUS_UNRELIABLE
override val orientation: StateFlow<Pair<Float, Float>> = _orientation override val orientation: StateFlow<Pair<Float, Float>> = _orientation
override fun enableSensor() { override fun enableSensor() {
sensor?.let { sensorManager.registerListener(this, it, 16000) } sensor?.let { sensorManager.registerListener(this, it, 8000) }
} }
override fun disableSensor() = sensorManager.unregisterListener(this) override fun disableSensor() = sensorManager.unregisterListener(this)
@@ -57,12 +57,10 @@ class SensorsRepo(private val sensorManager: SensorManager, private val sensor:
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector) SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector)
SensorManager.getOrientation(rotationMatrix, orientationValues) SensorManager.getOrientation(rotationMatrix, orientationValues)
val azimuth = (orientationValues[0] * RAD2DEG).toFloat() val azimuth = (orientationValues[0] * RAD2DEG).toFloat()
val pitch = (orientationValues[1] * RAD2DEG).toFloat() val pitch = (orientationValues[1] * RAD2DEG).toFloat() // roll [2]
// val roll = (orientationValues[2] * RAD2DEG).toFloat()
val magneticAzimuth = (azimuth + 360f) % 360f val magneticAzimuth = (azimuth + 360f) % 360f
val roundedAzimuth = round(magneticAzimuth * 10) / 10 val roundedAzimuth = round(magneticAzimuth * 10) / 10
val roundedPitch = round(pitch * 10) / 10 val roundedPitch = round(pitch * 10) / 10
// val roundedRoll = round(roll * 10) / 10
_orientation.value = Pair(roundedAzimuth, roundedPitch) _orientation.value = Pair(roundedAzimuth, roundedPitch)
} }
} }
@@ -49,8 +49,8 @@ class MapViewModel(
) : ViewModel() { ) : ViewModel() {
private val stationPos = settingsRepo.stationPosition.value private val stationPos = settingsRepo.stationPosition.value
private var allPasses = satelliteRepo.getPasses() private var allPasses = satelliteRepo.passes.value
private var allSatellites = listOf<Satellite>() private var allSatellites = satelliteRepo.satellites.value
private var dataUpdateJob: Job? = null private var dataUpdateJob: Job? = null
private var dataUpdateRate = 1000L private var dataUpdateRate = 1000L
private var selectedSatellite: Satellite? = null private var selectedSatellite: Satellite? = null
@@ -87,18 +87,12 @@ class MapViewModel(
} }
fun selectDefaultSatellite(catnum: Int) { fun selectDefaultSatellite(catnum: Int) {
viewModelScope.launch { if (allSatellites.isNotEmpty()) {
val selectedIds = settingsRepo.satelliteSelection.value if (catnum == -1) {
satelliteRepo.getEntriesWithIds(selectedIds).also { satellites -> allPasses.find { pass -> pass.progress < 100 && !pass.isDeepSpace }
if (satellites.isNotEmpty()) { ?.let { pass -> selectSatellite(pass.satellite) }
allSatellites = satellites } else {
if (catnum == -1) { allSatellites.find { it.data.catnum == catnum }?.let { selectSatellite(it) }
allPasses.find { pass -> pass.progress < 100 && !pass.isDeepSpace }
?.let { pass -> selectSatellite(pass.satellite) }
} else {
satellites.find { it.data.catnum == catnum }?.let { selectSatellite(it) }
}
}
} }
} }
} }
@@ -53,10 +53,9 @@ fun PassesScreen(navToRadar: (Int, Long) -> Unit) {
val showDialog = rememberSaveable { mutableStateOf(false) } val showDialog = rememberSaveable { mutableStateOf(false) }
val toggleDialog = { showDialog.value = showDialog.value.not() } val toggleDialog = { showDialog.value = showDialog.value.not() }
if (showDialog.value) { if (showDialog.value) {
FilterDialog(viewModel.getHoursAhead(), val (hoursAhead, minElevation) = viewModel.getFilterSettings()
viewModel.getMinElevation(), FilterDialog(hoursAhead, minElevation, { toggleDialog() }) { newHours, newElevation ->
{ toggleDialog() }) { hours, elev -> viewModel.calculatePasses(System.currentTimeMillis(), newHours, newElevation)
viewModel.calculatePasses(hours, elev)
} }
} }
@@ -26,6 +26,7 @@ import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.domain.ISatelliteRepo import com.rtbishop.look4sat.domain.ISatelliteRepo
import com.rtbishop.look4sat.domain.ISettingsRepo import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.DataState
import com.rtbishop.look4sat.model.PassesSettings
import com.rtbishop.look4sat.model.SatPass import com.rtbishop.look4sat.model.SatPass
import com.rtbishop.look4sat.utility.toTimerString import com.rtbishop.look4sat.utility.toTimerString
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -47,13 +48,14 @@ class PassesViewModel(
val passes: StateFlow<DataState<List<SatPass>>> = _passes val passes: StateFlow<DataState<List<SatPass>>> = _passes
val timerText: StateFlow<Triple<String, String, String>?> = _timerText val timerText: StateFlow<Triple<String, String, String>?> = _timerText
fun getHoursAhead() = settingsRepo.getHoursAhead() fun getFilterSettings() = settingsRepo.passesSettings.value
fun getMinElevation() = settingsRepo.getMinElevation() fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState
init { init {
viewModelScope.launch { satelliteRepo.initRepository() }
viewModelScope.launch { viewModelScope.launch {
satelliteRepo.calculatedPasses.collect { passes -> satelliteRepo.passes.collect { passes ->
passesProcessing?.cancelAndJoin() passesProcessing?.cancelAndJoin()
passesProcessing = viewModelScope.launch { passesProcessing = viewModelScope.launch {
while (isActive) { while (isActive) {
@@ -84,32 +86,20 @@ class PassesViewModel(
} }
} }
} }
viewModelScope.launch {
settingsRepo.satelliteSelection.collect { calculatePasses() }
}
} }
fun shouldUseUTC() = settingsRepo.otherSettings.value.utcState fun calculatePasses() = viewModelScope.launch {
_passes.emit(DataState.Loading)
passesProcessing?.cancelAndJoin()
val (hoursAhead, minElevation) = settingsRepo.passesSettings.value
satelliteRepo.calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation)
}
fun calculatePasses( fun calculatePasses(timeRef: Long, hoursAhead: Int, minElev: Double) = viewModelScope.launch {
hoursAhead: Int = 1, _passes.emit(DataState.Loading)
minElevation: Double = settingsRepo.getMinElevation(), passesProcessing?.cancelAndJoin()
timeRef: Long = System.currentTimeMillis() settingsRepo.savePassesSettings(PassesSettings(hoursAhead, minElev))
) { satelliteRepo.calculatePasses(timeRef, hoursAhead, minElev)
viewModelScope.launch {
_passes.emit(DataState.Loading)
// _timerText.postValue(timerDefaultText)
passesProcessing?.cancelAndJoin()
// selection?.let { items -> settingsRepository.saveEntriesSelection(items) }
settingsRepo.setHoursAhead(hoursAhead)
settingsRepo.setMinElevation(minElevation)
val stationPos = settingsRepo.stationPosition.value
val selectedIds = settingsRepo.satelliteSelection.value
val satellites = satelliteRepo.getEntriesWithIds(selectedIds)
satelliteRepo.calculatePasses(
satellites, stationPos, timeRef, hoursAhead, minElevation
)
}
} }
companion object { companion object {
@@ -28,8 +28,8 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.data.ISensorSource
import com.rtbishop.look4sat.domain.ISatelliteRepo import com.rtbishop.look4sat.domain.ISatelliteRepo
import com.rtbishop.look4sat.domain.ISensorsRepo
import com.rtbishop.look4sat.domain.ISettingsRepo import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.domain.Satellite import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.framework.BluetoothReporter import com.rtbishop.look4sat.framework.BluetoothReporter
@@ -51,20 +51,20 @@ 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 sensorSource: ISensorSource
) : ViewModel() { ) : ViewModel() {
private val stationPos = settingsRepo.stationPosition.value private val stationPos = settingsRepo.stationPosition.value
private val magDeclination = getMagDeclination(stationPos) private val magDeclination = getMagDeclination(stationPos)
val transmitters = mutableStateOf<List<SatRadio>>(emptyList()) val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
val radarData = mutableStateOf<RadarData?>(null) val radarData = mutableStateOf<RadarData?>(null)
val orientation = mutableStateOf(sensorsRepo.orientation.value) val orientation = mutableStateOf(sensorSource.orientation.value)
init { init {
if (settingsRepo.otherSettings.value.sensorState) { if (settingsRepo.otherSettings.value.sensorState) {
viewModelScope.launch { viewModelScope.launch {
sensorsRepo.enableSensor() sensorSource.enableSensor()
sensorsRepo.orientation.collect { data -> sensorSource.orientation.collect { data ->
orientation.value = Pair(data.first + magDeclination, data.second) orientation.value = Pair(data.first + magDeclination, data.second)
} }
} }
@@ -72,29 +72,28 @@ class RadarViewModel(
} }
override fun onCleared() { override fun onCleared() {
sensorsRepo.disableSensor() sensorSource.disableSensor()
super.onCleared() super.onCleared()
} }
fun getPass() = flow { fun getPass() = flow {
val catNum = savedStateHandle.get<Int>("catNum") ?: 0 val catNum = savedStateHandle.get<Int>("catNum") ?: 0
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
satelliteRepo.calculatedPasses.collect { passes -> val passes = satelliteRepo.passes.value
val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime } val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime }
val currentPass = pass ?: passes.firstOrNull() val currentPass = pass ?: passes.firstOrNull()
currentPass?.let { satPass -> currentPass?.let { satPass ->
emit(satPass) emit(satPass)
val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum) val transmitters = satelliteRepo.getRadiosWithId(satPass.catNum)
viewModelScope.launch { viewModelScope.launch {
while (isActive) { while (isActive) {
val timeNow = System.currentTimeMillis() val timeNow = System.currentTimeMillis()
val satPos = val satPos =
satelliteRepo.getPosition(satPass.satellite, stationPos, timeNow) satelliteRepo.getPosition(satPass.satellite, stationPos, timeNow)
sendPassData(satPass, satPos, satPass.satellite) sendPassData(satPass, satPos, satPass.satellite)
sendPassDataBT(satPos) sendPassDataBT(satPos)
processRadios(transmitters, satPass.satellite, timeNow) processRadios(transmitters, satPass.satellite, timeNow)
delay(1000) delay(1000)
}
} }
} }
} }
@@ -165,7 +164,7 @@ class RadarViewModel(
container.provideNetworkReporter(), container.provideNetworkReporter(),
container.satelliteRepo, container.satelliteRepo,
container.settingsRepo, container.settingsRepo,
container.sensorsRepo container.provideSensorSource()
) )
} }
} }
@@ -1,8 +1,8 @@
package com.rtbishop.look4sat.domain package com.rtbishop.look4sat.data
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface ISensorsRepo { interface ISensorSource {
val orientation: StateFlow<Pair<Float, Float>> val orientation: StateFlow<Pair<Float, Float>>
@@ -18,6 +18,7 @@
package com.rtbishop.look4sat.data package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.ISatelliteRepo import com.rtbishop.look4sat.domain.ISatelliteRepo
import com.rtbishop.look4sat.domain.ISettingsRepo
import com.rtbishop.look4sat.domain.Satellite import com.rtbishop.look4sat.domain.Satellite
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.SatPass import com.rtbishop.look4sat.model.SatPass
@@ -26,25 +27,33 @@ import com.rtbishop.look4sat.model.SatRadio
import com.rtbishop.look4sat.utility.round import com.rtbishop.look4sat.utility.round
import com.rtbishop.look4sat.utility.toDegrees import com.rtbishop.look4sat.utility.toDegrees
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SatelliteRepo( class SatelliteRepo(
private val dispatcher: CoroutineDispatcher, private val dispatcher: CoroutineDispatcher,
private val localStorage: ILocalStorage private val localStorage: ILocalStorage,
private val settingsRepo: ISettingsRepo
) : ISatelliteRepo { ) : ISatelliteRepo {
private var passes = listOf<SatPass>() private val _passes = MutableStateFlow<List<SatPass>>(emptyList())
private val _calculatedPasses = MutableSharedFlow<List<SatPass>>(replay = 1) override val passes: StateFlow<List<SatPass>> = _passes
override val calculatedPasses: SharedFlow<List<SatPass>> = _calculatedPasses
override fun getPasses(): List<SatPass> = passes private val _satellites = MutableStateFlow<List<Satellite>>(emptyList())
override val satellites: StateFlow<List<Satellite>> = _satellites
override suspend fun getEntriesWithIds(ids: List<Int>) = localStorage.getEntriesWithIds(ids)
override suspend fun getRadiosWithId(id: Int) = localStorage.getRadiosWithId(id) override suspend fun getRadiosWithId(id: Int) = localStorage.getRadiosWithId(id)
override suspend fun initRepository() = withContext(dispatcher) {
settingsRepo.satelliteSelection.collect { selectedIds ->
_satellites.update { localStorage.getEntriesWithIds(selectedIds) }
val (hoursAhead, minElevation) = settingsRepo.passesSettings.value
calculatePasses(System.currentTimeMillis(), hoursAhead, minElevation)
}
}
override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos { override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos {
return withContext(dispatcher) { sat.getPosition(pos, time) } return withContext(dispatcher) { sat.getPosition(pos, time) }
} }
@@ -70,12 +79,8 @@ class SatelliteRepo(
val satPos = sat.getPosition(pos, time) val satPos = sat.getPosition(pos, time)
val copiedList = radios.map { it.copy() } val copiedList = radios.map { it.copy() }
copiedList.forEach { transmitter -> copiedList.forEach { transmitter ->
transmitter.downlink?.let { transmitter.downlink?.let { transmitter.downlink = satPos.getDownlinkFreq(it) }
transmitter.downlink = satPos.getDownlinkFreq(it) transmitter.uplink?.let { transmitter.uplink = satPos.getUplinkFreq(it) }
}
transmitter.uplink?.let {
transmitter.uplink = satPos.getUplinkFreq(it)
}
} }
copiedList.map { it.copy() } copiedList.map { it.copy() }
} }
@@ -97,23 +102,18 @@ class SatelliteRepo(
} }
} }
override suspend fun calculatePasses( override suspend fun calculatePasses(time: Long, hoursAhead: Int, minElevation: Double) {
satList: List<Satellite>, pos: GeoPos, time: Long, hoursAhead: Int, minElevation: Double if (_satellites.value.isNotEmpty()) {
) {
if (satList.isEmpty()) {
val newPasses = emptyList<SatPass>()
passes = newPasses
_calculatedPasses.emit(newPasses)
} else {
withContext(dispatcher) { withContext(dispatcher) {
val allPasses = mutableListOf<SatPass>() val allPasses = mutableListOf<SatPass>()
satList.forEach { satellite -> val stationPos = settingsRepo.stationPosition.value
allPasses.addAll(satellite.getPasses(pos, time, hoursAhead)) _satellites.value.forEach { satellite ->
allPasses.addAll(satellite.getPasses(stationPos, time, hoursAhead))
} }
val newPasses = allPasses.filter(time, hoursAhead, minElevation) _passes.update { allPasses.filter(time, hoursAhead, minElevation) }
passes = newPasses
_calculatedPasses.emit(newPasses)
} }
} else {
_passes.update { emptyList() }
} }
} }
@@ -151,8 +151,7 @@ class SatelliteRepo(
private fun getGeoPass(sat: Satellite, pos: GeoPos, time: Long): SatPass { private fun getGeoPass(sat: Satellite, pos: GeoPos, time: Long): SatPass {
val satPos = sat.getPosition(pos, time) val satPos = sat.getPosition(pos, time)
val aos = time - 24 * 60L * 60L * 1000L val aos = time - 24 * 60L * 60L * 1000L
val los = time + 24 * 60L * 60L * 1000L val los = time + 24 * 60L * 60L * 1000L // val tca = (aos + los) / 2
// val tca = (aos + los) / 2
val az = satPos.azimuth.toDegrees().round(1) val az = satPos.azimuth.toDegrees().round(1)
val elev = satPos.elevation.toDegrees().round(1) val elev = satPos.elevation.toDegrees().round(1)
val alt = satPos.altitude val alt = satPos.altitude
@@ -164,8 +163,7 @@ class SatelliteRepo(
var calendarTimeMillis = time var calendarTimeMillis = time
var elevation: Double var elevation: Double
var maxElevation = 0.0 var maxElevation = 0.0
var alt = 0.0 var alt = 0.0 // var tcaAz = 0.0
// var tcaAz = 0.0
// rewind 1/4 of an orbit // rewind 1/4 of an orbit
if (rewind) calendarTimeMillis += -quarterOrbitMin * 60L * 1000L if (rewind) calendarTimeMillis += -quarterOrbitMin * 60L * 1000L
@@ -187,8 +185,7 @@ class SatelliteRepo(
elevation = satPos.elevation elevation = satPos.elevation
if (elevation > maxElevation) { if (elevation > maxElevation) {
maxElevation = elevation maxElevation = elevation
alt = satPos.altitude alt = satPos.altitude // tcaAz = satPos.azimuth.toDegrees()
// tcaAz = satPos.azimuth.toDegrees()
} }
} while (satPos.elevation < 0.0) } while (satPos.elevation < 0.0)
@@ -200,8 +197,7 @@ class SatelliteRepo(
elevation = satPos.elevation elevation = satPos.elevation
if (elevation > maxElevation) { if (elevation > maxElevation) {
maxElevation = elevation maxElevation = elevation
alt = satPos.altitude alt = satPos.altitude // tcaAz = satPos.azimuth.toDegrees()
// tcaAz = satPos.azimuth.toDegrees()
} }
} while (satPos.elevation < 0.0) } while (satPos.elevation < 0.0)
@@ -215,8 +211,7 @@ class SatelliteRepo(
elevation = satPos.elevation elevation = satPos.elevation
if (elevation > maxElevation) { if (elevation > maxElevation) {
maxElevation = elevation maxElevation = elevation
alt = satPos.altitude alt = satPos.altitude // tcaAz = satPos.azimuth.toDegrees()
// tcaAz = satPos.azimuth.toDegrees()
} }
} while (satPos.elevation > 0.0) } while (satPos.elevation > 0.0)
@@ -228,14 +223,12 @@ class SatelliteRepo(
elevation = satPos.elevation elevation = satPos.elevation
if (elevation > maxElevation) { if (elevation > maxElevation) {
maxElevation = elevation maxElevation = elevation
alt = satPos.altitude alt = satPos.altitude // tcaAz = satPos.azimuth.toDegrees()
// tcaAz = satPos.azimuth.toDegrees()
} }
} while (satPos.elevation > 0.0) } while (satPos.elevation > 0.0)
val los = satPos.time val los = satPos.time // val tca = (aos + los) / 2
val losAz = satPos.azimuth.toDegrees().round(1) val losAz = satPos.azimuth.toDegrees().round(1)
// val tca = (aos + los) / 2
val elev = maxElevation.toDegrees().round(1) val elev = maxElevation.toDegrees().round(1)
return SatPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat) return SatPass(aos, aosAz, los, losAz, alt.toInt(), elev, sat)
} }
@@ -4,36 +4,27 @@ import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.SatPass import com.rtbishop.look4sat.model.SatPass
import com.rtbishop.look4sat.model.SatPos import com.rtbishop.look4sat.model.SatPos
import com.rtbishop.look4sat.model.SatRadio import com.rtbishop.look4sat.model.SatRadio
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow
interface ISatelliteRepo { interface ISatelliteRepo {
val calculatedPasses: SharedFlow<List<SatPass>> val passes: StateFlow<List<SatPass>>
fun getPasses(): List<SatPass> val satellites: StateFlow<List<Satellite>>
suspend fun getEntriesWithIds(ids: List<Int>): List<Satellite>
suspend fun getRadiosWithId(id: Int): List<SatRadio> suspend fun getRadiosWithId(id: Int): List<SatRadio>
suspend fun initRepository()
suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos
suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos> suspend fun getTrack(sat: Satellite, pos: GeoPos, start: Long, end: Long): List<SatPos>
suspend fun processRadios( suspend fun processRadios(
sat: Satellite, sat: Satellite, pos: GeoPos, radios: List<SatRadio>, time: Long
pos: GeoPos,
radios: List<SatRadio>,
time: Long
): List<SatRadio> ): List<SatRadio>
suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass> suspend fun processPasses(passList: List<SatPass>, time: Long): List<SatPass>
suspend fun calculatePasses( suspend fun calculatePasses(time: Long, hoursAhead: Int = 8, minElevation: Double = 16.0)
satList: List<Satellite>,
pos: GeoPos,
time: Long,
hoursAhead: Int = 8,
minElevation: Double = 16.0
)
} }
@@ -20,12 +20,12 @@ package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.model.DatabaseState import com.rtbishop.look4sat.model.DatabaseState
import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.GeoPos
import com.rtbishop.look4sat.model.OtherSettings import com.rtbishop.look4sat.model.OtherSettings
import com.rtbishop.look4sat.model.PassesSettings
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
interface ISettingsRepo { interface ISettingsRepo {
val radioSourceUrl: String get() = "https://db.satnogs.org/api/transmitters/?format=json" val radioSourceUrl: String get() = "https://db.satnogs.org/api/transmitters/?format=json"
val satelliteSourcesMap: Map<String, String> val satelliteSourcesMap: Map<String, String>
get() = mapOf( get() = mapOf(
"All" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", "All" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
@@ -56,9 +56,9 @@ interface ISettingsRepo {
"X-Comm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv" "X-Comm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv"
) )
val stationPosition: StateFlow<GeoPos> //region # Station position settings
val satelliteSelection: StateFlow<List<Int>> val stationPosition: StateFlow<GeoPos>
fun setGpsPosition(): Boolean fun setGpsPosition(): Boolean
@@ -66,18 +66,42 @@ interface ISettingsRepo {
fun setQthPosition(locator: String): Boolean fun setQthPosition(locator: String): Boolean
fun getHoursAhead(): Int //endregion
fun setHoursAhead(hoursAhead: Int) //region # Database update settings
fun getMinElevation(): Double
fun setMinElevation(minElevation: Double)
val databaseState: StateFlow<DatabaseState> val databaseState: StateFlow<DatabaseState>
fun saveDatabaseState(state: DatabaseState) fun saveDatabaseState(state: DatabaseState)
fun saveSatType(type: String, catnums: List<Int>)
fun loadSatType(type: String): List<Int>
//endregion
//region # Entries selection settings
val satelliteSelection: StateFlow<List<Int>>
fun saveEntriesSelection(catnums: List<Int>)
//endregion
//region # Passes filter settings
val passesSettings: StateFlow<PassesSettings>
fun savePassesSettings(settings: PassesSettings)
fun saveModesSelection(modes: List<String>)
fun loadModesSelection(): List<String>
//endregion
//region # Other settings
val otherSettings: StateFlow<OtherSettings> val otherSettings: StateFlow<OtherSettings>
fun toggleUtc(value: Boolean) fun toggleUtc(value: Boolean)
@@ -88,15 +112,9 @@ interface ISettingsRepo {
fun toggleSensor(value: Boolean) fun toggleSensor(value: Boolean)
fun saveModesSelection(modes: List<String>) //endregion
fun loadModesSelection(): List<String> //region # Undefined settings
fun saveEntriesSelection(catnums: List<Int>)
fun saveSatType(type: String, catnums: List<Int>)
fun loadSatType(type: String): List<Int>
fun getRotatorEnabled(): Boolean fun getRotatorEnabled(): Boolean
@@ -125,4 +143,6 @@ interface ISettingsRepo {
fun getBTFormat(): String fun getBTFormat(): String
fun setBTFormat(value: String) fun setBTFormat(value: String)
//endregion
} }
@@ -1,10 +1,12 @@
package com.rtbishop.look4sat.model package com.rtbishop.look4sat.model
data class DatabaseState(val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long)
data class PassesSettings(val hoursAhead: Int, val minElevation: Double)
data class OtherSettings( data class OtherSettings(
val utcState: Boolean, val utcState: Boolean,
val updateState: Boolean, val updateState: Boolean,
val sweepState: Boolean, val sweepState: Boolean,
val sensorState: Boolean val sensorState: Boolean
) )
data class DatabaseState(val entriesTotal: Int, val radiosTotal: Int, val timestamp: Long)