mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Further classes movement and cleanup
This commit is contained in:
1 parent
542cfcfd50
commit
622d4e3632
27 files changed
+684
-578
No files matched your search
@@ -19,7 +19,7 @@ package com.rtbishop.look4sat
|
||||
|
||||
import android.app.Application
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -33,7 +33,7 @@ class MainApplication : Application() {
|
||||
lateinit var dataRepository: IDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var settingsSource: ISettingsSource
|
||||
lateinit var settingsRepository: ISettingsRepository
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -44,8 +44,8 @@ class MainApplication : Application() {
|
||||
}
|
||||
|
||||
private fun checkAutoUpdate() {
|
||||
if (settingsSource.isUpdateEnabled()) {
|
||||
val timeDelta = System.currentTimeMillis() - settingsSource.getLastUpdateTime()
|
||||
if (settingsRepository.isUpdateEnabled()) {
|
||||
val timeDelta = System.currentTimeMillis() - settingsRepository.getLastUpdateTime()
|
||||
if (timeDelta > 172800000) { // 48 hours in ms
|
||||
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
|
||||
Timber.d("Started periodic data update on ${sdf.format(Date())}")
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.location.LocationManager
|
||||
import androidx.room.Room
|
||||
import com.rtbishop.look4sat.data.DataParser
|
||||
import com.rtbishop.look4sat.data.DataRepository
|
||||
import com.rtbishop.look4sat.data.SatelliteRepository
|
||||
import com.rtbishop.look4sat.domain.*
|
||||
import com.rtbishop.look4sat.framework.*
|
||||
import com.rtbishop.look4sat.framework.data.*
|
||||
@@ -55,12 +56,14 @@ object MainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkReporter(): NetworkReporter = NetworkReporter(CoroutineScope(Dispatchers.IO))
|
||||
fun provideNetworkReporter(): NetworkReporter {
|
||||
return NetworkReporter(CoroutineScope(Dispatchers.IO))
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDataRepository(
|
||||
@ApplicationContext context: Context, settings: ISettingsSource
|
||||
@ApplicationContext context: Context, settings: ISettingsRepository
|
||||
): IDataRepository {
|
||||
val db = Room.databaseBuilder(context, MainDatabase::class.java, "Look4SatDb")
|
||||
.addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build()
|
||||
@@ -75,28 +78,23 @@ object MainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideLocationSource(
|
||||
@ApplicationContext context: Context, settings: ISettingsSource
|
||||
): ILocationSource {
|
||||
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return LocationSource(manager, settings)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOrientationSource(@ApplicationContext context: Context): OrientationSource {
|
||||
fun provideSensorRepository(@ApplicationContext context: Context): ISensorRepository {
|
||||
val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
|
||||
return OrientationSource(manager, sensor)
|
||||
return SensorRepository(manager, sensor)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSatelliteManager(): ISatelliteManager = SatelliteManager(Dispatchers.Default)
|
||||
fun provideSatelliteRepository(): ISatelliteRepository {
|
||||
return SatelliteRepository(Dispatchers.Default)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSettingsSource(@ApplicationContext context: Context): ISettingsSource {
|
||||
return SettingsSource(context.getSharedPreferences("default", Context.MODE_PRIVATE))
|
||||
fun provideSettingsRepository(@ApplicationContext context: Context): ISettingsRepository {
|
||||
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
val preferences = context.getSharedPreferences("default", Context.MODE_PRIVATE)
|
||||
return SettingsRepository(manager, preferences)
|
||||
}
|
||||
}
|
||||
@@ -1,84 +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.framework
|
||||
|
||||
import android.location.Criteria
|
||||
import android.location.LocationManager
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.os.CancellationSignal
|
||||
import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.utility.QthConverter
|
||||
import com.rtbishop.look4sat.utility.round
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class LocationSource(
|
||||
private val manager: LocationManager,
|
||||
private val settings: ISettingsSource,
|
||||
) : ILocationSource {
|
||||
|
||||
private val _stationPosition = MutableStateFlow(settings.loadStationPosition())
|
||||
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private val timeoutSignal = CancellationSignal().apply {
|
||||
setOnCancelListener { _stationPosition.value = _stationPosition.value }
|
||||
}
|
||||
override val stationPosition: StateFlow<GeoPos> = _stationPosition
|
||||
|
||||
override fun setGpsPosition(): Boolean {
|
||||
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
|
||||
try {
|
||||
val criteria = Criteria().apply { isCostAllowed = true }
|
||||
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
||||
Timber.d("Requesting location for $provider provider")
|
||||
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
|
||||
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
|
||||
}
|
||||
} catch (exception: SecurityException) {
|
||||
Timber.d("No permissions were given")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
|
||||
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
|
||||
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
|
||||
saveGeoPos(latitude, newLongitude, altitude, locator)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setQthPosition(locator: String): Boolean {
|
||||
val position = QthConverter.qthToPosition(locator) ?: return false
|
||||
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun saveGeoPos(latitude: Double, longitude: Double, altitude: Double, locator: String) {
|
||||
val newLat = latitude.round(4)
|
||||
val newLon = longitude.round(4)
|
||||
val newAlt = altitude.round(1)
|
||||
val timestamp = System.currentTimeMillis()
|
||||
Timber.d("Received new Position($newLat, $newLon, $newAlt) & Locator $locator")
|
||||
_stationPosition.value = GeoPos(newLat, newLon, newAlt, locator, timestamp)
|
||||
settings.saveStationPosition(stationPosition.value)
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -21,27 +21,27 @@ import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import com.rtbishop.look4sat.domain.ISensorRepository
|
||||
import com.rtbishop.look4sat.domain.RAD2DEG
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlin.math.round
|
||||
|
||||
class OrientationSource(
|
||||
private val manager: SensorManager, private val sensor: Sensor?
|
||||
) : SensorEventListener {
|
||||
class SensorRepository(private val manager: SensorManager, private val sensor: Sensor?) :
|
||||
SensorEventListener, ISensorRepository {
|
||||
|
||||
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))
|
||||
val orientation: StateFlow<Pair<Float, Float>> = _orientation
|
||||
override val orientation: StateFlow<Pair<Float, Float>> = _orientation
|
||||
|
||||
fun startListening() = sensor?.let {
|
||||
manager.registerListener(this, it, 16000)
|
||||
override fun enableSensor() {
|
||||
sensor?.let { manager.registerListener(this, it, 16000) }
|
||||
}
|
||||
|
||||
fun stopListening() = manager.unregisterListener(this)
|
||||
override fun disableSensor() = manager.unregisterListener(this)
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
|
||||
sensorAccuracy = accuracy
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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.framework
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.location.Criteria
|
||||
import android.location.LocationManager
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.os.CancellationSignal
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.utility.QthConverter
|
||||
import com.rtbishop.look4sat.utility.round
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class SettingsRepository(
|
||||
private val manager: LocationManager, private val preferences: SharedPreferences
|
||||
) : ISettingsRepository {
|
||||
|
||||
private val keyModes = "satModes"
|
||||
private val keyCompass = "compass"
|
||||
private val keyRadarSweep = "radarSweep"
|
||||
private val keyTimeUTC = "timeUTC"
|
||||
private val keyLastUpdateTime = "lastUpdateTime"
|
||||
private val keyAutoUpdateEnabled = "autoUpdateEnabled"
|
||||
private val keyHoursAhead = "hoursAhead"
|
||||
private val keyMinElevation = "minElevation"
|
||||
private val keyRotator = "isRotatorEnabled"
|
||||
private val keyRotatorAddress = "rotatorAddress"
|
||||
private val keyRotatorPort = "rotatorPort"
|
||||
private val keyBTEnabled = "isBTEnabled"
|
||||
private val keyBTDeviceName = "BTDeviceName"
|
||||
private val keyBTDeviceAddr = "BTDeviceAddr"
|
||||
private val keyBTFormat = "BTFormat"
|
||||
private val keyLatitude = "stationLat"
|
||||
private val keyLongitude = "stationLon"
|
||||
private val keyAltitude = "stationAlt"
|
||||
private val keyLocator = "stationQTH"
|
||||
private val keyLocTimestamp = "locTimestamp"
|
||||
private val keySelection = "selection"
|
||||
|
||||
//region # Station position settings
|
||||
|
||||
private val _stationPosition = MutableStateFlow(loadStationPosition())
|
||||
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private val timeoutSignal = CancellationSignal().apply {
|
||||
setOnCancelListener { _stationPosition.value = loadStationPosition() }
|
||||
}
|
||||
override val stationPosition: StateFlow<GeoPos> = _stationPosition
|
||||
|
||||
override fun setGpsPosition(): Boolean {
|
||||
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
|
||||
try {
|
||||
val criteria = Criteria().apply { isCostAllowed = true }
|
||||
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
||||
Timber.d("Requesting location for $provider provider")
|
||||
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
|
||||
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
|
||||
}
|
||||
} catch (exception: SecurityException) {
|
||||
Timber.d("No permissions were given")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
|
||||
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
|
||||
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
|
||||
saveGeoPos(latitude, newLongitude, altitude, locator)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setQthPosition(locator: String): Boolean {
|
||||
val position = QthConverter.qthToPosition(locator) ?: return false
|
||||
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
|
||||
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 {
|
||||
putString(keyLatitude, stationPos.latitude.toString())
|
||||
putString(keyLongitude, stationPos.longitude.toString())
|
||||
putString(keyAltitude, stationPos.altitude.toString())
|
||||
putString(keyLocator, stationPos.qthLocator)
|
||||
putLong(keyLocTimestamp, stationPos.timestamp)
|
||||
_stationPosition.value = stationPos
|
||||
}
|
||||
|
||||
private fun saveGeoPos(latitude: Double, longitude: Double, altitude: Double, locator: String) {
|
||||
val newLat = latitude.round(4)
|
||||
val newLon = longitude.round(4)
|
||||
val newAlt = altitude.round(1)
|
||||
val timestamp = System.currentTimeMillis()
|
||||
Timber.d("Received new Position($newLat, $newLon, $newAlt) & Locator $locator")
|
||||
saveStationPosition(GeoPos(newLat, newLon, newAlt, locator, timestamp))
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
override fun saveEntriesSelection(catnums: List<Int>) {
|
||||
val stringList = catnums.map { catnum -> catnum.toString() }
|
||||
preferences.edit { putStringSet(keySelection, stringList.toSet()) }
|
||||
}
|
||||
|
||||
override fun loadEntriesSelection(): List<Int> {
|
||||
val catnums =
|
||||
preferences.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() }
|
||||
return catnums?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
override fun getHoursAhead(): Int {
|
||||
return preferences.getInt(keyHoursAhead, 24)
|
||||
}
|
||||
|
||||
override fun setHoursAhead(hoursAhead: Int) {
|
||||
preferences.edit { putInt(keyHoursAhead, hoursAhead) }
|
||||
}
|
||||
|
||||
override fun getMinElevation(): Double {
|
||||
return preferences.getDouble(keyMinElevation, 16.0)
|
||||
}
|
||||
|
||||
override fun setMinElevation(minElevation: Double) {
|
||||
preferences.edit { putDouble(keyMinElevation, minElevation) }
|
||||
}
|
||||
|
||||
override fun isUtcEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyTimeUTC, false)
|
||||
}
|
||||
|
||||
override fun setUtcState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyTimeUTC, value) }
|
||||
}
|
||||
|
||||
override fun getLastUpdateTime(): Long {
|
||||
return preferences.getLong(keyLastUpdateTime, 0L)
|
||||
}
|
||||
|
||||
override fun setLastUpdateTime(updateTime: Long) {
|
||||
preferences.edit { putLong(keyLastUpdateTime, updateTime) }
|
||||
}
|
||||
|
||||
override fun isUpdateEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyAutoUpdateEnabled, true)
|
||||
}
|
||||
|
||||
override fun setUpdateState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyAutoUpdateEnabled, value) }
|
||||
}
|
||||
|
||||
override fun isSensorEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyCompass, true)
|
||||
}
|
||||
|
||||
override fun setSensorState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyCompass, value) }
|
||||
}
|
||||
|
||||
override fun isSweepEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyRadarSweep, true)
|
||||
}
|
||||
|
||||
override fun setSweepState(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyRadarSweep, value) }
|
||||
}
|
||||
|
||||
override fun saveModesSelection(modes: List<String>) {
|
||||
preferences.edit { putStringSet(keyModes, modes.toSet()) }
|
||||
}
|
||||
|
||||
override fun loadModesSelection(): List<String> {
|
||||
return preferences.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
override fun getRotatorEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyRotator, false)
|
||||
}
|
||||
|
||||
override fun setRotatorEnabled(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyRotator, value) }
|
||||
}
|
||||
|
||||
override fun getRotatorServer(): String {
|
||||
return preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
|
||||
}
|
||||
|
||||
override fun setRotatorServer(value: String) {
|
||||
preferences.edit { putString(keyRotatorAddress, value) }
|
||||
}
|
||||
|
||||
override fun getRotatorPort(): String {
|
||||
return preferences.getString(keyRotatorPort, null) ?: "4533"
|
||||
}
|
||||
|
||||
override fun setRotatorPort(value: String) {
|
||||
preferences.edit { putString(keyRotatorPort, value) }
|
||||
}
|
||||
|
||||
override fun getBTEnabled(): Boolean {
|
||||
return preferences.getBoolean(keyBTEnabled, false)
|
||||
}
|
||||
|
||||
override fun setBTEnabled(value: Boolean) {
|
||||
preferences.edit { putBoolean(keyBTEnabled, value) }
|
||||
}
|
||||
|
||||
override fun getBTDeviceAddr(): String {
|
||||
return preferences.getString(keyBTDeviceAddr, null) ?: "00:0C:BF:13:80:5D"
|
||||
}
|
||||
|
||||
override fun setBTDeviceAddr(value: String) {
|
||||
preferences.edit { putString(keyBTDeviceAddr, value) }
|
||||
}
|
||||
|
||||
override fun getBTDeviceName(): String {
|
||||
return preferences.getString(keyBTDeviceName, null) ?: "Default"
|
||||
}
|
||||
|
||||
override fun setBTDeviceName(value: String) {
|
||||
preferences.edit { putString(keyBTDeviceName, value) }
|
||||
}
|
||||
|
||||
override fun getBTFormat(): String {
|
||||
return preferences.getString(keyBTFormat, null) ?: "W\$AZ \$EL"
|
||||
}
|
||||
|
||||
override fun setBTFormat(value: String) {
|
||||
preferences.edit { putString(keyBTFormat, value) }
|
||||
}
|
||||
|
||||
private fun SharedPreferences.getDouble(key: String, default: Double): Double {
|
||||
return Double.fromBits(getLong(key, default.toRawBits()))
|
||||
}
|
||||
|
||||
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
|
||||
putLong(key, double.toRawBits())
|
||||
}
|
||||
}
|
||||
@@ -1,238 +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.framework
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
|
||||
class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
|
||||
|
||||
companion object {
|
||||
const val keyModes = "satModes"
|
||||
const val keyCompass = "compass"
|
||||
const val keyRadarSweep = "radarSweep"
|
||||
const val keyTimeUTC = "timeUTC"
|
||||
const val keyLastUpdateTime = "lastUpdateTime"
|
||||
const val keyAutoUpdateEnabled = "autoUpdateEnabled"
|
||||
const val keyHoursAhead = "hoursAhead"
|
||||
const val keyMinElevation = "minElevation"
|
||||
const val keyRotator = "isRotatorEnabled"
|
||||
const val keyRotatorAddress = "rotatorAddress"
|
||||
const val keyRotatorPort = "rotatorPort"
|
||||
const val keyBTEnabled = "isBTEnabled"
|
||||
const val keyBTDeviceName = "BTDeviceName"
|
||||
const val keyBTDeviceAddr = "BTDeviceAddr"
|
||||
const val keyBTFormat = "BTFormat"
|
||||
const val keyLatitude = "stationLat"
|
||||
const val keyLongitude = "stationLon"
|
||||
const val keyAltitude = "stationAlt"
|
||||
const val keyLocator = "stationQTH"
|
||||
const val keyLocTimestamp = "locTimestamp"
|
||||
const val keySelection = "selection"
|
||||
}
|
||||
|
||||
override fun loadStationPosition(): GeoPos {
|
||||
val latitude = (prefs.getString(keyLatitude, null) ?: "0.0").toDouble()
|
||||
val longitude = (prefs.getString(keyLongitude, null) ?: "0.0").toDouble()
|
||||
val altitude = (prefs.getString(keyAltitude, null) ?: "0.0").toDouble()
|
||||
val qthLocator = prefs.getString(keyLocator, null) ?: "null"
|
||||
val timestamp = prefs.getLong(keyLocTimestamp, 0L)
|
||||
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
|
||||
}
|
||||
|
||||
override fun saveStationPosition(stationPos: GeoPos) = prefs.edit {
|
||||
putString(keyLatitude, stationPos.latitude.toString())
|
||||
putString(keyLongitude, stationPos.longitude.toString())
|
||||
putString(keyAltitude, stationPos.altitude.toString())
|
||||
putString(keyLocator, stationPos.qthLocator)
|
||||
putLong(keyLocTimestamp, stationPos.timestamp)
|
||||
}
|
||||
|
||||
override fun saveEntriesSelection(catnums: List<Int>) {
|
||||
val stringList = catnums.map { catnum -> catnum.toString() }
|
||||
prefs.edit { putStringSet(keySelection, stringList.toSet()) }
|
||||
}
|
||||
|
||||
override fun loadEntriesSelection(): List<Int> {
|
||||
val catnums = prefs.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() }
|
||||
return catnums?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
override fun saveSatType(type: String, catnums: List<Int>) {
|
||||
val stringList = catnums.map { catnum -> catnum.toString() }
|
||||
prefs.edit { putStringSet("type$type", stringList.toSet()) }
|
||||
}
|
||||
|
||||
override fun loadSatType(type: String): List<Int> {
|
||||
val catnums = prefs.getStringSet("type$type", emptySet())?.map { catnum -> catnum.toInt() }
|
||||
return catnums ?: emptyList()
|
||||
}
|
||||
|
||||
override fun getHoursAhead(): Int {
|
||||
return prefs.getInt(keyHoursAhead, 24)
|
||||
}
|
||||
|
||||
override fun setHoursAhead(hoursAhead: Int) {
|
||||
prefs.edit { putInt(keyHoursAhead, hoursAhead) }
|
||||
}
|
||||
|
||||
override fun getMinElevation(): Double {
|
||||
return prefs.getDouble(keyMinElevation, 16.0)
|
||||
}
|
||||
|
||||
override fun setMinElevation(minElevation: Double) {
|
||||
prefs.edit { putDouble(keyMinElevation, minElevation) }
|
||||
}
|
||||
|
||||
override fun isUtcEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyTimeUTC, false)
|
||||
}
|
||||
|
||||
override fun setUtcState(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyTimeUTC, value) }
|
||||
}
|
||||
|
||||
override fun getLastUpdateTime(): Long {
|
||||
return prefs.getLong(keyLastUpdateTime, 0L)
|
||||
}
|
||||
|
||||
override fun setLastUpdateTime(updateTime: Long) {
|
||||
prefs.edit { putLong(keyLastUpdateTime, updateTime) }
|
||||
}
|
||||
|
||||
override fun isUpdateEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyAutoUpdateEnabled, true)
|
||||
}
|
||||
|
||||
override fun setUpdateState(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyAutoUpdateEnabled, value) }
|
||||
}
|
||||
|
||||
override fun isSensorEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyCompass, true)
|
||||
}
|
||||
|
||||
override fun setSensorState(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyCompass, value) }
|
||||
}
|
||||
|
||||
override fun isSweepEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyRadarSweep, true)
|
||||
}
|
||||
|
||||
override fun setSweepState(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyRadarSweep, value) }
|
||||
}
|
||||
|
||||
override fun saveModesSelection(modes: List<String>) {
|
||||
prefs.edit { putStringSet(keyModes, modes.toSet()) }
|
||||
}
|
||||
|
||||
override fun loadModesSelection(): List<String> {
|
||||
return prefs.getStringSet(keyModes, null)?.toList()?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
override fun getRotatorEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyRotator, false)
|
||||
}
|
||||
|
||||
override fun setRotatorEnabled(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyRotator, value) }
|
||||
}
|
||||
|
||||
override fun getRotatorServer(): String {
|
||||
return prefs.getString(keyRotatorAddress, null) ?: "127.0.0.1"
|
||||
}
|
||||
|
||||
override fun setRotatorServer(value: String) {
|
||||
prefs.edit { putString(keyRotatorAddress, value) }
|
||||
}
|
||||
|
||||
override fun getRotatorPort(): String {
|
||||
return prefs.getString(keyRotatorPort, null) ?: "4533"
|
||||
}
|
||||
|
||||
override fun setRotatorPort(value: String) {
|
||||
prefs.edit { putString(keyRotatorPort, value) }
|
||||
}
|
||||
|
||||
override fun getBTEnabled(): Boolean {
|
||||
return prefs.getBoolean(keyBTEnabled, false)
|
||||
}
|
||||
|
||||
override fun setBTEnabled(value: Boolean) {
|
||||
prefs.edit { putBoolean(keyBTEnabled, value) }
|
||||
}
|
||||
|
||||
override fun getBTDeviceAddr(): String {
|
||||
return prefs.getString(keyBTDeviceAddr, null) ?: "00:0C:BF:13:80:5D"
|
||||
}
|
||||
|
||||
override fun setBTDeviceAddr(value: String) {
|
||||
prefs.edit { putString(keyBTDeviceAddr, value) }
|
||||
}
|
||||
|
||||
override fun getBTDeviceName(): String {
|
||||
return prefs.getString(keyBTDeviceName, null) ?: "Default"
|
||||
}
|
||||
|
||||
override fun setBTDeviceName(value: String) {
|
||||
prefs.edit { putString(keyBTDeviceName, value) }
|
||||
}
|
||||
|
||||
override fun getBTFormat(): String {
|
||||
return prefs.getString(keyBTFormat, null) ?: "W\$AZ \$EL"
|
||||
}
|
||||
|
||||
override fun setBTFormat(value: String) {
|
||||
prefs.edit { putString(keyBTFormat, value) }
|
||||
}
|
||||
|
||||
private fun SharedPreferences.getDouble(key: String, default: Double): Double {
|
||||
return Double.fromBits(getLong(key, default.toRawBits()))
|
||||
}
|
||||
|
||||
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
|
||||
putLong(key, double.toRawBits())
|
||||
}
|
||||
|
||||
inline fun <reified T> SharedPreferences.observeKey(key: String, default: T): Flow<T> {
|
||||
val flow = MutableStateFlow(getItem(key, default))
|
||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, k ->
|
||||
if (key == k) flow.value = getItem(key, default)
|
||||
}
|
||||
registerOnSharedPreferenceChangeListener(listener)
|
||||
return flow.onCompletion { unregisterOnSharedPreferenceChangeListener(listener) }
|
||||
}
|
||||
|
||||
inline fun <reified T> SharedPreferences.getItem(key: String, default: T): T {
|
||||
return when (default) {
|
||||
is Boolean -> getBoolean(key, default) as T
|
||||
is Int -> getInt(key, default) as T
|
||||
is Long -> getLong(key, default) as T
|
||||
is Float -> getFloat(key, default) as T
|
||||
is String -> getString(key, default) as T
|
||||
else -> throw IllegalArgumentException("Could not handle ${T::class.java.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,9 @@ package com.rtbishop.look4sat.presentation.entries
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.domain.ISatelliteRepository
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.model.SatItem
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -37,10 +35,9 @@ import javax.inject.Inject
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
class EntriesViewModel @Inject constructor(
|
||||
private val satelliteManager: ISatelliteManager,
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsSource,
|
||||
private val locationSource: ILocationSource
|
||||
private val dataRepository: IDataRepository,
|
||||
private val satelliteRepository: ISatelliteRepository,
|
||||
private val settingsRepository: ISettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val satType = MutableStateFlow("All")
|
||||
@@ -53,12 +50,12 @@ class EntriesViewModel @Inject constructor(
|
||||
itemsWithType.map { items -> filterByQuery(items, query) }
|
||||
}
|
||||
val satData = itemsWithQuery.map { items -> DataState.Success(items) }
|
||||
val satTypes: List<String> = settings.sourcesMap.keys.sorted()
|
||||
val satTypes = dataRepository.getSatelliteTypes()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
delay(250)
|
||||
itemsFromRepo.value = loadEntriesWithSelection()
|
||||
itemsFromRepo.value = dataRepository.getEntriesWithSelection()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,15 +75,14 @@ class EntriesViewModel @Inject constructor(
|
||||
|
||||
fun saveSelection() = viewModelScope.launch {
|
||||
val newSelection = itemsFromRepo.value.filter { it.isSelected }.map { it.catnum }
|
||||
settings.saveEntriesSelection(newSelection)
|
||||
val selectedIds = settings.loadEntriesSelection()
|
||||
val satellites = repository.getEntriesWithIds(selectedIds)
|
||||
val stationPos = locationSource.stationPosition.value
|
||||
val geoPos = GeoPos(stationPos.latitude, stationPos.longitude)
|
||||
settingsRepository.saveEntriesSelection(newSelection)
|
||||
val selectedIds = settingsRepository.loadEntriesSelection()
|
||||
val satellites = dataRepository.getEntriesWithIds(selectedIds)
|
||||
val stationPos = settingsRepository.stationPosition.value
|
||||
val timeRef = System.currentTimeMillis()
|
||||
val hoursAhead = settings.getHoursAhead()
|
||||
val minElev = settings.getMinElevation()
|
||||
satelliteManager.calculatePasses(satellites, geoPos, timeRef, hoursAhead, minElev)
|
||||
val hoursAhead = settingsRepository.getHoursAhead()
|
||||
val minElev = settingsRepository.getMinElevation()
|
||||
satelliteRepository.calculatePasses(satellites, stationPos, timeRef, hoursAhead, minElev)
|
||||
}
|
||||
|
||||
fun updateSelection(catNums: List<Int>, isSelected: Boolean) {
|
||||
@@ -99,14 +95,9 @@ class EntriesViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadEntriesWithSelection(): List<SatItem> {
|
||||
val selectedIds = settings.loadEntriesSelection()
|
||||
return repository.getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds }
|
||||
}
|
||||
|
||||
private fun filterByType(items: List<SatItem>, type: String): List<SatItem> {
|
||||
if (type == "All") return items
|
||||
val catnums = settings.loadSatType(type)
|
||||
val catnums = settingsRepository.loadSatType(type)
|
||||
if (catnums.isEmpty()) return items
|
||||
return items.filter { item -> item.catnum in catnums }
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package com.rtbishop.look4sat.presentation.map
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.*
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ISatelliteRepository
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import com.rtbishop.look4sat.domain.Satellite
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.model.SatPos
|
||||
import com.rtbishop.look4sat.utility.QthConverter
|
||||
@@ -38,15 +41,14 @@ import kotlin.math.min
|
||||
|
||||
@HiltViewModel
|
||||
class MapViewModel @Inject constructor(
|
||||
private val repository: IDataRepository,
|
||||
private val satelliteManager: ISatelliteManager,
|
||||
private val settings: ISettingsSource,
|
||||
private val locationSource: ILocationSource
|
||||
private val dataRepository: IDataRepository,
|
||||
private val satelliteRepository: ISatelliteRepository,
|
||||
private val settingsRepository: ISettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val stationPosNew = locationSource.stationPosition.value
|
||||
private val stationPosNew = settingsRepository.stationPosition.value
|
||||
private val stationPosition = GeoPos(stationPosNew.latitude, stationPosNew.longitude)
|
||||
private var allPasses = satelliteManager.getPasses()
|
||||
private var allPasses = satelliteRepository.getPasses()
|
||||
private var allSatellites = listOf<Satellite>()
|
||||
private var dataUpdateJob: Job? = null
|
||||
private var dataUpdateRate = 1000L
|
||||
@@ -85,8 +87,8 @@ class MapViewModel @Inject constructor(
|
||||
|
||||
fun selectDefaultSatellite(catnum: Int) {
|
||||
viewModelScope.launch {
|
||||
val selectedIds = settings.loadEntriesSelection()
|
||||
repository.getEntriesWithIds(selectedIds).also { satellites ->
|
||||
val selectedIds = settingsRepository.loadEntriesSelection()
|
||||
dataRepository.getEntriesWithIds(selectedIds).also { satellites ->
|
||||
if (satellites.isNotEmpty()) {
|
||||
allSatellites = satellites
|
||||
if (catnum == -1) {
|
||||
@@ -123,7 +125,7 @@ class MapViewModel @Inject constructor(
|
||||
val currentTrack = mutableListOf<GeoPos>()
|
||||
val endDate = Date(date.time + (satellite.data.orbitalPeriod * 2.4 * 60000L).toLong())
|
||||
var oldLongitude = 0.0
|
||||
satelliteManager.getTrack(satellite, pos, date.time, endDate.time).forEach { satPos ->
|
||||
satelliteRepository.getTrack(satellite, pos, date.time, endDate.time).forEach { satPos ->
|
||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||
val currentPosition = GeoPos(osmLat, osmLon)
|
||||
@@ -150,7 +152,7 @@ class MapViewModel @Inject constructor(
|
||||
private suspend fun getPositions(satellites: List<Satellite>, pos: GeoPos, date: Date) {
|
||||
val positions = mutableMapOf<Satellite, GeoPos>()
|
||||
satellites.forEach { satellite ->
|
||||
val satPos = satelliteManager.getPosition(satellite, pos, date.time)
|
||||
val satPos = satelliteRepository.getPosition(satellite, pos, date.time)
|
||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||
positions[satellite] = GeoPos(osmLat, osmLon)
|
||||
@@ -159,7 +161,7 @@ class MapViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun getSatFootprint(satellite: Satellite, pos: GeoPos, date: Date) {
|
||||
val satPos = satelliteManager.getPosition(satellite, pos, date.time)
|
||||
val satPos = satelliteRepository.getPosition(satellite, pos, date.time)
|
||||
_footprint.emit(satPos)
|
||||
}
|
||||
|
||||
@@ -177,7 +179,7 @@ class MapViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
val satPos = satelliteManager.getPosition(sat, pos, date.time)
|
||||
val satPos = satelliteRepository.getPosition(sat, pos, date.time)
|
||||
val azimuth = satPos.azimuth.toDegrees()
|
||||
val elevation = satPos.elevation.toDegrees()
|
||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||
|
||||
@@ -22,7 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.domain.NearEarthSat
|
||||
import com.rtbishop.look4sat.domain.NearEarthSatellite
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import com.rtbishop.look4sat.model.OrbitalData
|
||||
import com.rtbishop.look4sat.model.SatPass
|
||||
@@ -100,7 +100,7 @@ private fun PassPreview() {
|
||||
val data = OrbitalData(
|
||||
"Satellite", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 45000, 0.0
|
||||
)
|
||||
val satellite = NearEarthSat(data)
|
||||
val satellite = NearEarthSatellite(data)
|
||||
val pass = SatPass(1L, 25.0, 10L, 75.0, 850, 45.0, satellite, 0.5f)
|
||||
MainTheme { Pass(pass = pass, { _,_ -> }) }
|
||||
}
|
||||
|
||||
@@ -20,11 +20,9 @@ package com.rtbishop.look4sat.presentation.passes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISatelliteManager
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.domain.ISatelliteRepository
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.model.SatPass
|
||||
import com.rtbishop.look4sat.utility.toTimerString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@@ -35,10 +33,9 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class PassesViewModel @Inject constructor(
|
||||
private val satelliteManager: ISatelliteManager,
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsSource,
|
||||
private val locationSource: ILocationSource
|
||||
private val dataRepository: IDataRepository,
|
||||
private val satelliteRepository: ISatelliteRepository,
|
||||
private val settingsRepository: ISettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _passes = MutableStateFlow<DataState<List<SatPass>>>(DataState.Loading)
|
||||
@@ -48,18 +45,18 @@ class PassesViewModel @Inject constructor(
|
||||
val passes: StateFlow<DataState<List<SatPass>>> = _passes
|
||||
val timerText: StateFlow<Triple<String, String, String>?> = _timerText
|
||||
|
||||
fun getHoursAhead() = settings.getHoursAhead()
|
||||
fun getHoursAhead() = settingsRepository.getHoursAhead()
|
||||
|
||||
fun getMinElevation() = settings.getMinElevation()
|
||||
fun getMinElevation() = settingsRepository.getMinElevation()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
satelliteManager.calculatedPasses.collect { passes ->
|
||||
satelliteRepository.calculatedPasses.collect { passes ->
|
||||
passesProcessing?.cancelAndJoin()
|
||||
passesProcessing = viewModelScope.launch {
|
||||
while (isActive) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
val newPasses = satelliteManager.processPasses(passes, timeNow)
|
||||
val newPasses = satelliteRepository.processPasses(passes, timeNow)
|
||||
|
||||
if (newPasses.isNotEmpty()) {
|
||||
try {
|
||||
@@ -86,18 +83,18 @@ class PassesViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
repository.updateState.collect { updateState ->
|
||||
dataRepository.updateState.collect { updateState ->
|
||||
if (updateState is DataState.Success) calculatePasses()
|
||||
}
|
||||
}
|
||||
calculatePasses()
|
||||
}
|
||||
|
||||
fun shouldUseUTC() = settings.isUtcEnabled()
|
||||
fun shouldUseUTC() = settingsRepository.isUtcEnabled()
|
||||
|
||||
fun calculatePasses(
|
||||
hoursAhead: Int = 1,
|
||||
minElevation: Double = settings.getMinElevation(),
|
||||
minElevation: Double = settingsRepository.getMinElevation(),
|
||||
timeRef: Long = System.currentTimeMillis(),
|
||||
selection: List<Int>? = null
|
||||
) {
|
||||
@@ -105,15 +102,14 @@ class PassesViewModel @Inject constructor(
|
||||
_passes.emit(DataState.Loading)
|
||||
// _timerText.postValue(timerDefaultText)
|
||||
passesProcessing?.cancelAndJoin()
|
||||
selection?.let { items -> settings.saveEntriesSelection(items) }
|
||||
settings.setHoursAhead(hoursAhead)
|
||||
settings.setMinElevation(minElevation)
|
||||
val stationPos = locationSource.stationPosition.value
|
||||
val geoPos = GeoPos(stationPos.latitude, stationPos.longitude)
|
||||
val selectedIds = settings.loadEntriesSelection()
|
||||
val satellites = repository.getEntriesWithIds(selectedIds)
|
||||
satelliteManager.calculatePasses(
|
||||
satellites, geoPos, timeRef, hoursAhead, minElevation
|
||||
selection?.let { items -> settingsRepository.saveEntriesSelection(items) }
|
||||
settingsRepository.setHoursAhead(hoursAhead)
|
||||
settingsRepository.setMinElevation(minElevation)
|
||||
val stationPos = settingsRepository.stationPosition.value
|
||||
val selectedIds = settingsRepository.loadEntriesSelection()
|
||||
val satellites = dataRepository.getEntriesWithIds(selectedIds)
|
||||
satelliteRepository.calculatePasses(
|
||||
satellites, stationPos, timeRef, hoursAhead, minElevation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.domain.*
|
||||
import com.rtbishop.look4sat.framework.BluetoothReporter
|
||||
import com.rtbishop.look4sat.framework.NetworkReporter
|
||||
import com.rtbishop.look4sat.framework.OrientationSource
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.model.SatPass
|
||||
import com.rtbishop.look4sat.model.SatPos
|
||||
@@ -42,27 +41,26 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class RadarViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val orientationSource: OrientationSource,
|
||||
private val reporter: NetworkReporter,
|
||||
private val bluetoothReporter: BluetoothReporter,
|
||||
private val satManager: ISatelliteManager,
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsSource,
|
||||
private val locationSource: ILocationSource
|
||||
private val networkReporter: NetworkReporter,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val dataRepository: IDataRepository,
|
||||
private val satelliteRepository: ISatelliteRepository,
|
||||
private val sensorRepository: ISensorRepository,
|
||||
private val settingsRepository: ISettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val stationPos = locationSource.stationPosition.value
|
||||
private val stationPos = settingsRepository.stationPosition.value
|
||||
private val magDeclination = getMagDeclination(stationPos)
|
||||
val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
|
||||
val radarData = mutableStateOf<RadarData?>(null)
|
||||
val orientation = mutableStateOf(orientationSource.orientation.value)
|
||||
val orientation = mutableStateOf(sensorRepository.orientation.value)
|
||||
|
||||
init {
|
||||
if (settings.isSensorEnabled()) {
|
||||
if (settingsRepository.isSensorEnabled()) {
|
||||
viewModelScope.launch {
|
||||
orientationSource.startListening()
|
||||
orientationSource.orientation.collect { data ->
|
||||
sensorRepository.enableSensor()
|
||||
sensorRepository.orientation.collect { data ->
|
||||
orientation.value = Pair(data.first + magDeclination, data.second)
|
||||
}
|
||||
}
|
||||
@@ -70,23 +68,24 @@ class RadarViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
orientationSource.stopListening()
|
||||
sensorRepository.disableSensor()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun getPass() = flow {
|
||||
val catNum = savedStateHandle.get<Int>("catNum") ?: 0
|
||||
val aosTime = savedStateHandle.get<Long>("aosTime") ?: 0L
|
||||
satManager.calculatedPasses.collect { passes ->
|
||||
satelliteRepository.calculatedPasses.collect { passes ->
|
||||
val pass = passes.find { pass -> pass.catNum == catNum && pass.aosTime == aosTime }
|
||||
val currentPass = pass ?: passes.firstOrNull()
|
||||
currentPass?.let { satPass ->
|
||||
emit(satPass)
|
||||
val transmitters = repository.getRadiosWithId(satPass.catNum)
|
||||
val transmitters = dataRepository.getRadiosWithId(satPass.catNum)
|
||||
viewModelScope.launch {
|
||||
while (isActive) {
|
||||
val timeNow = System.currentTimeMillis()
|
||||
val satPos = satManager.getPosition(satPass.satellite, stationPos, timeNow)
|
||||
val satPos =
|
||||
satelliteRepository.getPosition(satPass.satellite, stationPos, timeNow)
|
||||
sendPassData(satPass, satPos, satPass.satellite)
|
||||
sendPassDataBT(satPos)
|
||||
processRadios(transmitters, satPass.satellite, timeNow)
|
||||
@@ -97,9 +96,9 @@ class RadarViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun getUseCompass(): Boolean = settings.isSensorEnabled()
|
||||
fun getUseCompass(): Boolean = settingsRepository.isSensorEnabled()
|
||||
|
||||
fun getShowSweep(): Boolean = settings.isSweepEnabled()
|
||||
fun getShowSweep(): Boolean = settingsRepository.isSweepEnabled()
|
||||
|
||||
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
||||
val latitude = geoPos.latitude.toFloat()
|
||||
@@ -111,14 +110,16 @@ class RadarViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
var track: List<SatPos> = emptyList()
|
||||
if (!satPass.isDeepSpace) {
|
||||
track = satManager.getTrack(satellite, stationPos, satPass.aosTime, satPass.losTime)
|
||||
track = satelliteRepository.getTrack(
|
||||
satellite, stationPos, satPass.aosTime, satPass.losTime
|
||||
)
|
||||
}
|
||||
if (settings.getRotatorEnabled()) {
|
||||
val server = settings.getRotatorServer()
|
||||
val port = settings.getRotatorPort().toInt()
|
||||
if (settingsRepository.getRotatorEnabled()) {
|
||||
val server = settingsRepository.getRotatorServer()
|
||||
val port = settingsRepository.getRotatorPort().toInt()
|
||||
val azimuth = satPos.azimuth.toDegrees().round(1)
|
||||
val elevation = satPos.elevation.toDegrees().round(1)
|
||||
reporter.reportRotation(server, port, azimuth, elevation)
|
||||
networkReporter.reportRotation(server, port, azimuth, elevation)
|
||||
}
|
||||
radarData.value = RadarData(satPos, track)
|
||||
}
|
||||
@@ -126,10 +127,10 @@ class RadarViewModel @Inject constructor(
|
||||
|
||||
private fun sendPassDataBT(satPos: SatPos) {
|
||||
viewModelScope.launch {
|
||||
if (settings.getBTEnabled()) {
|
||||
val btDevice = settings.getBTDeviceAddr()
|
||||
if (settingsRepository.getBTEnabled()) {
|
||||
val btDevice = settingsRepository.getBTDeviceAddr()
|
||||
if (bluetoothReporter.isConnected()) {
|
||||
val format = settings.getBTFormat()
|
||||
val format = settingsRepository.getBTFormat()
|
||||
val azimuth = satPos.azimuth.toDegrees().round(0).toInt()
|
||||
val elevation = satPos.elevation.toDegrees().round(0).toInt()
|
||||
bluetoothReporter.reportRotation(format, azimuth, elevation)
|
||||
@@ -144,7 +145,7 @@ class RadarViewModel @Inject constructor(
|
||||
private fun processRadios(radios: List<SatRadio>, satellite: Satellite, time: Long) {
|
||||
viewModelScope.launch {
|
||||
delay(125)
|
||||
val list = satManager.processRadios(satellite, stationPos, radios, time)
|
||||
val list = satelliteRepository.processRadios(satellite, stationPos, radios, time)
|
||||
transmitters.value = list
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -165,9 +166,8 @@ private fun CardAbout(version: String, modifier: Modifier = Modifier) {
|
||||
@Composable
|
||||
private fun LocationCardPreview() = MainTheme {
|
||||
val stationPos = GeoPos(0.0, 0.0, 0.0, "IO91vl", 0L)
|
||||
LocationCard(
|
||||
settings = LocationSettings(true, stationPos, {}, { _, _ -> }, { }),
|
||||
setGpsLoc = {}, togglePosDialog = {}) {}
|
||||
val settings = LocationSettings(true, stationPos, 0, {}, { _, _ -> }, {}, {})
|
||||
LocationCard(settings = settings, setGpsLoc = {}, togglePosDialog = {}) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -231,14 +231,21 @@ private fun LocationCard(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (settings.messageResId != 0) {
|
||||
val context = LocalContext.current
|
||||
val errorString = stringResource(id = settings.messageResId)
|
||||
LaunchedEffect(key1 = settings.messageResId) {
|
||||
showToast(context, errorString)
|
||||
settings.dismissMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DataCardPreview() = MainTheme {
|
||||
DataCard(settings = DataSettings(
|
||||
true, 0L, 5000, 2500, {}, { }, {})
|
||||
) {}
|
||||
val settings = DataSettings(true, 0L, 5000, 2500, {}, {}, {})
|
||||
DataCard(settings = settings) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -335,10 +342,10 @@ private fun DataCard(settings: DataSettings, updateFromFile: () -> Unit) {
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun OtherCardPreview() = MainTheme {
|
||||
OtherCard(settings = OtherSettings(
|
||||
val settings = OtherSettings(
|
||||
getUtc = false, getUpdate = true, getSweep = true, getSensor = true,
|
||||
setUtc = {}, setUpdate = {}, setSweep = {}, setSensor = {})
|
||||
)
|
||||
OtherCard(settings = settings)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+3
-1
@@ -5,9 +5,11 @@ import com.rtbishop.look4sat.model.GeoPos
|
||||
data class LocationSettings(
|
||||
val isUpdating: Boolean,
|
||||
val stationPos: GeoPos,
|
||||
val messageResId: Int,
|
||||
val setGpsLoc: () -> Unit,
|
||||
val setManualLoc: (Double, Double) -> Unit,
|
||||
val setQthLoc: (String) -> Unit
|
||||
val setQthLoc: (String) -> Unit,
|
||||
val dismissMessage: () -> Unit
|
||||
)
|
||||
|
||||
data class DataSettings(
|
||||
|
||||
+61
-28
@@ -20,9 +20,9 @@ package com.rtbishop.look4sat.presentation.settings
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.domain.IDataRepository
|
||||
import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsRepository
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -31,31 +31,32 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
private val locationSource: ILocationSource,
|
||||
private val repository: IDataRepository,
|
||||
private val settings: ISettingsSource
|
||||
private val dataRepository: IDataRepository,
|
||||
private val settingsRepository: ISettingsRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val defaultLocationSettings = LocationSettings(false,
|
||||
locationSource.stationPosition.value,
|
||||
{ updateStationPosition() },
|
||||
{ lat, lon -> locationSource.setGeoPosition(lat, lon) },
|
||||
{ locationSource.setQthPosition(it) })
|
||||
settingsRepository.stationPosition.value,
|
||||
0,
|
||||
{ setGpsPosition() },
|
||||
{ latitude, longitude -> setGeoPosition(latitude, longitude) },
|
||||
{ locator -> setQthPosition(locator) },
|
||||
{ dismissLocationMessage() })
|
||||
val locationSettings = mutableStateOf(defaultLocationSettings)
|
||||
|
||||
private val defaultDataSettings = DataSettings(false,
|
||||
settings.getLastUpdateTime(),
|
||||
settingsRepository.getLastUpdateTime(),
|
||||
0,
|
||||
0,
|
||||
{ repository.updateFromWeb() },
|
||||
{ repository.updateFromFile(it) },
|
||||
{ repository.clearAllData() })
|
||||
{ dataRepository.updateFromWeb() },
|
||||
{ dataRepository.updateFromFile(it) },
|
||||
{ dataRepository.clearAllData() })
|
||||
val dataSettings = mutableStateOf(defaultDataSettings)
|
||||
|
||||
private val defaultOtherSettings = OtherSettings(settings.isUtcEnabled(),
|
||||
settings.isUpdateEnabled(),
|
||||
settings.isSweepEnabled(),
|
||||
settings.isSensorEnabled(),
|
||||
private val defaultOtherSettings = OtherSettings(settingsRepository.isUtcEnabled(),
|
||||
settingsRepository.isUpdateEnabled(),
|
||||
settingsRepository.isSweepEnabled(),
|
||||
settingsRepository.isSensorEnabled(),
|
||||
{ setUtc(it) },
|
||||
{ setUpdate(it) },
|
||||
{ setSweep(it) },
|
||||
@@ -64,49 +65,81 @@ class SettingsViewModel @Inject constructor(
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
locationSource.stationPosition.collect { stationPos ->
|
||||
settingsRepository.stationPosition.collect { stationPos ->
|
||||
locationSettings.value = locationSettings.value.copy(
|
||||
isUpdating = false, stationPos = stationPos
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
repository.updateState.collect {
|
||||
dataRepository.updateState.collect {
|
||||
val isUpdating = it is DataState.Loading
|
||||
dataSettings.value = dataSettings.value.copy(
|
||||
isUpdating = isUpdating, lastUpdated = settings.getLastUpdateTime()
|
||||
isUpdating = isUpdating, lastUpdated = settingsRepository.getLastUpdateTime()
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
combine(repository.getEntriesTotal(), repository.getRadiosTotal()) { sats, radios ->
|
||||
combine(
|
||||
dataRepository.getEntriesTotal(), dataRepository.getRadiosTotal()
|
||||
) { sats, radios ->
|
||||
dataSettings.value = dataSettings.value.copy(satsTotal = sats, radiosTotal = radios)
|
||||
}.collect {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStationPosition() {
|
||||
val isSuccessful = locationSource.setGpsPosition()
|
||||
locationSettings.value = locationSettings.value.copy(isUpdating = isSuccessful)
|
||||
private fun setGpsPosition() {
|
||||
if (settingsRepository.setGpsPosition()) {
|
||||
val messageResId = R.string.location_success
|
||||
locationSettings.value =
|
||||
locationSettings.value.copy(isUpdating = true, messageResId = messageResId)
|
||||
} else {
|
||||
val errorResId = R.string.location_gps_error
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = errorResId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setGeoPosition(latitude: Double, longitude: Double) {
|
||||
if (settingsRepository.setGeoPosition(latitude, longitude)) {
|
||||
val messageResId = R.string.location_success
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = messageResId)
|
||||
} else {
|
||||
val errorResId = R.string.location_manual_error
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = errorResId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setQthPosition(locator: String) {
|
||||
if (settingsRepository.setQthPosition(locator)) {
|
||||
val messageResId = R.string.location_success
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = messageResId)
|
||||
} else {
|
||||
val errorResId = R.string.location_qth_error
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = errorResId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissLocationMessage() {
|
||||
locationSettings.value = locationSettings.value.copy(messageResId = 0)
|
||||
}
|
||||
|
||||
private fun setUtc(value: Boolean) {
|
||||
settings.setUtcState(value)
|
||||
settingsRepository.setUtcState(value)
|
||||
otherSettings.value = otherSettings.value.copy(getUtc = value)
|
||||
}
|
||||
|
||||
private fun setUpdate(value: Boolean) {
|
||||
settings.setUpdateState(value)
|
||||
settingsRepository.setUpdateState(value)
|
||||
otherSettings.value = otherSettings.value.copy(getUpdate = value)
|
||||
}
|
||||
|
||||
private fun setSweep(value: Boolean) {
|
||||
settings.setSweepState(value)
|
||||
settingsRepository.setSweepState(value)
|
||||
otherSettings.value = otherSettings.value.copy(getSweep = value)
|
||||
}
|
||||
|
||||
private fun setSensor(value: Boolean) {
|
||||
settings.setSensorState(value)
|
||||
settingsRepository.setSensorState(value)
|
||||
otherSettings.value = otherSettings.value.copy(getSensor = value)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user