From 622d4e363244c26e0ed3a1f2d0193f6f8530fab9 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sun, 19 Mar 2023 12:15:32 +0000 Subject: [PATCH] Further classes movement and cleanup --- .../com/rtbishop/look4sat/MainApplication.kt | 8 +- .../java/com/rtbishop/look4sat/MainModule.kt | 30 +- .../look4sat/framework/LocationSource.kt | 84 ------ ...ientationSource.kt => SensorRepository.kt} | 14 +- .../look4sat/framework/SettingsRepository.kt | 277 ++++++++++++++++++ .../look4sat/framework/SettingsSource.kt | 238 --------------- .../presentation/entries/EntriesViewModel.kt | 39 +-- .../look4sat/presentation/map/MapViewModel.kt | 28 +- .../presentation/passes/PassesScreen.kt | 4 +- .../presentation/passes/PassesViewModel.kt | 44 ++- .../presentation/radar/RadarViewModel.kt | 57 ++-- .../presentation/settings/SettingsScreen.kt | 23 +- .../settings/SettingsScreenState.kt | 4 +- .../settings/SettingsViewModel.kt | 89 ++++-- .../com/rtbishop/look4sat/data/DataParser.kt | 91 +++--- .../rtbishop/look4sat/data/DataRepository.kt | 24 +- .../rtbishop/look4sat/data/INetworkSource.kt | 31 +- .../SatelliteRepository.kt} | 16 +- ...{DeepSpaceSat.kt => DeepSpaceSatellite.kt} | 2 +- .../look4sat/domain/IDataRepository.kt | 4 + .../look4sat/domain/ILocationSource.kt | 32 -- ...liteManager.kt => ISatelliteRepository.kt} | 2 +- .../look4sat/domain/ISensorRepository.kt | 12 + .../look4sat/domain/ISettingsRepository.kt | 100 +++++++ ...{NearEarthSat.kt => NearEarthSatellite.kt} | 2 +- .../com/rtbishop/look4sat/domain/Satellite.kt | 4 +- .../rtbishop/look4sat/model/OrbitalData.kt | 3 +- 27 files changed, 684 insertions(+), 578 deletions(-) delete mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt rename app/src/main/java/com/rtbishop/look4sat/framework/{OrientationSource.kt => SensorRepository.kt} (84%) create mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/SettingsRepository.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/SettingsSource.kt rename base/src/main/java/com/rtbishop/look4sat/{domain/SatelliteManager.kt => data/SatelliteRepository.kt} (94%) rename base/src/main/java/com/rtbishop/look4sat/domain/{DeepSpaceSat.kt => DeepSpaceSatellite.kt} (99%) delete mode 100644 base/src/main/java/com/rtbishop/look4sat/domain/ILocationSource.kt rename base/src/main/java/com/rtbishop/look4sat/domain/{ISatelliteManager.kt => ISatelliteRepository.kt} (96%) create mode 100644 base/src/main/java/com/rtbishop/look4sat/domain/ISensorRepository.kt create mode 100644 base/src/main/java/com/rtbishop/look4sat/domain/ISettingsRepository.kt rename base/src/main/java/com/rtbishop/look4sat/domain/{NearEarthSat.kt => NearEarthSatellite.kt} (99%) diff --git a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt index 7ef73f5c..e6fa2a15 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt @@ -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())}") diff --git a/app/src/main/java/com/rtbishop/look4sat/MainModule.kt b/app/src/main/java/com/rtbishop/look4sat/MainModule.kt index 553d627f..5d9729be 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainModule.kt @@ -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) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt deleted file mode 100644 index cf08b955..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt +++ /dev/null @@ -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 . - */ -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 = _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) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/OrientationSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SensorRepository.kt similarity index 84% rename from app/src/main/java/com/rtbishop/look4sat/framework/OrientationSource.kt rename to app/src/main/java/com/rtbishop/look4sat/framework/SensorRepository.kt index 5d46789f..08c43c39 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/OrientationSource.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/SensorRepository.kt @@ -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> = _orientation + override val orientation: StateFlow> = _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 diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsRepository.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsRepository.kt new file mode 100644 index 00000000..c41b4e5c --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsRepository.kt @@ -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 . + */ +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 = _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) { + val stringList = catnums.map { catnum -> catnum.toString() } + preferences.edit { putStringSet(keySelection, stringList.toSet()) } + } + + override fun loadEntriesSelection(): List { + val catnums = + preferences.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() } + return catnums?.sorted() ?: emptyList() + } + + override fun saveSatType(type: String, catnums: List) { + val stringList = catnums.map { catnum -> catnum.toString() } + preferences.edit { putStringSet("type$type", stringList.toSet()) } + } + + override fun loadSatType(type: String): List { + 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) { + preferences.edit { putStringSet(keyModes, modes.toSet()) } + } + + override fun loadModesSelection(): List { + 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()) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsSource.kt deleted file mode 100644 index b91d96a8..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsSource.kt +++ /dev/null @@ -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 . - */ -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) { - val stringList = catnums.map { catnum -> catnum.toString() } - prefs.edit { putStringSet(keySelection, stringList.toSet()) } - } - - override fun loadEntriesSelection(): List { - val catnums = prefs.getStringSet(keySelection, emptySet())?.map { catnum -> catnum.toInt() } - return catnums?.sorted() ?: emptyList() - } - - override fun saveSatType(type: String, catnums: List) { - val stringList = catnums.map { catnum -> catnum.toString() } - prefs.edit { putStringSet("type$type", stringList.toSet()) } - } - - override fun loadSatType(type: String): List { - 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) { - prefs.edit { putStringSet(keyModes, modes.toSet()) } - } - - override fun loadModesSelection(): List { - 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 SharedPreferences.observeKey(key: String, default: T): Flow { - 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 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}") - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt index 817597f5..971195c7 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entries/EntriesViewModel.kt @@ -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 = 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, isSelected: Boolean) { @@ -99,14 +95,9 @@ class EntriesViewModel @Inject constructor( } } - private suspend fun loadEntriesWithSelection(): List { - val selectedIds = settings.loadEntriesSelection() - return repository.getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds } - } - private fun filterByType(items: List, type: String): List { 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 } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt index b6136e3a..aefcc08f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapViewModel.kt @@ -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() 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() 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, pos: GeoPos, date: Date) { val positions = mutableMapOf() 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()) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index 2de6b221..a27d5e38 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -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, { _,_ -> }) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt index dda0663d..4df8c25a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesViewModel.kt @@ -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.Loading) @@ -48,18 +45,18 @@ class PassesViewModel @Inject constructor( val passes: StateFlow>> = _passes val timerText: StateFlow?> = _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? = 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 ) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt index db660f75..d6cc8c62 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radar/RadarViewModel.kt @@ -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>(emptyList()) val radarData = mutableStateOf(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("catNum") ?: 0 val aosTime = savedStateHandle.get("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 = 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, 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 } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt index 2a444f22..95113885 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt @@ -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 diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreenState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreenState.kt index 890fc30b..ab7e34d8 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreenState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreenState.kt @@ -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( diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt index 26cc98ba..92839665 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt @@ -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) } diff --git a/base/src/main/java/com/rtbishop/look4sat/data/DataParser.kt b/base/src/main/java/com/rtbishop/look4sat/data/DataParser.kt index 4ecd5f4a..62388ad6 100644 --- a/base/src/main/java/com/rtbishop/look4sat/data/DataParser.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/DataParser.kt @@ -26,56 +26,52 @@ import org.json.JSONObject import java.io.InputStream import kotlin.math.pow -class DataParser(private val defaultDispatcher: CoroutineDispatcher) { +class DataParser(private val dispatcher: CoroutineDispatcher) { - suspend fun parseCSVStream(csvStream: InputStream): List = - withContext(defaultDispatcher) { - val parsedItems = mutableListOf() - csvStream.bufferedReader().useLines { lines -> - lines.forEachIndexed { index, line -> - if (index != 0) { - val values = line.split(",") - parseCSV(values)?.let { tle -> parsedItems.add(tle) } - } + suspend fun parseCSVStream(stream: InputStream): List = withContext(dispatcher) { + val parsedItems = mutableListOf() + stream.bufferedReader().useLines { lines -> + lines.forEachIndexed { index, line -> + if (index != 0) { + val values = line.split(",") + parseCSV(values)?.let { tle -> parsedItems.add(tle) } } } + } + return@withContext parsedItems + } + + suspend fun parseTLEStream(stream: InputStream): List = withContext(dispatcher) { + val tleStrings = mutableListOf(String(), String(), String()) + val parsedItems = mutableListOf() + var lineIndex = 0 + stream.bufferedReader().forEachLine { line -> + tleStrings[lineIndex] = line + if (lineIndex < 2) { + lineIndex++ + } else { + val isLineOneValid = tleStrings[1].substring(0, 1) == "1" + val isLineTwoValid = tleStrings[2].substring(0, 1) == "2" + if (!isLineOneValid && !isLineTwoValid) return@forEachLine + parseTLE(tleStrings)?.let { tle -> parsedItems.add(tle) } + lineIndex = 0 + } + } + return@withContext parsedItems + } + + suspend fun parseJSONStream(stream: InputStream): List = withContext(dispatcher) { + val parsedItems = mutableListOf() + try { + val jsonArray = JSONArray(stream.bufferedReader().readText()) + for (index in 0 until jsonArray.length()) { + val jsonObject = jsonArray.getJSONObject(index) + parseJSON(jsonObject)?.let { parsedItems.add(it) } + } return@withContext parsedItems - } - - suspend fun parseTLEStream(tleStream: InputStream): List = - withContext(defaultDispatcher) { - val tleStrings = mutableListOf(String(), String(), String()) - val parsedItems = mutableListOf() - var lineIndex = 0 - tleStream.bufferedReader().forEachLine { line -> - tleStrings[lineIndex] = line - if (lineIndex < 2) { - lineIndex++ - } else { - val isLineOneValid = tleStrings[1].substring(0, 1) == "1" - val isLineTwoValid = tleStrings[2].substring(0, 1) == "2" - if (!isLineOneValid && !isLineTwoValid) return@forEachLine - parseTLE(tleStrings)?.let { tle -> parsedItems.add(tle) } - lineIndex = 0 - } - } + } catch (exception: Exception) { return@withContext parsedItems } - - suspend fun parseJSONStream(jsonStream: InputStream): List { - return withContext(defaultDispatcher) { - val parsedItems = mutableListOf() - try { - val jsonArray = JSONArray(jsonStream.bufferedReader().readText()) - for (index in 0 until jsonArray.length()) { - val jsonObject = jsonArray.getJSONObject(index) - parseJSON(jsonObject)?.let { parsedItems.add(it) } - } - return@withContext parsedItems - } catch (exception: Exception) { - return@withContext parsedItems - } - } } private fun parseCSV(values: List): OrbitalData? { @@ -120,8 +116,9 @@ class DataParser(private val defaultDispatcher: CoroutineDispatcher) { val argper: Double = tle[2].substring(34, 42).toDouble() val meanan: Double = tle[2].substring(43, 51).toDouble() val catnum: Int = tle[1].substring(2, 7).trim().toInt() - val bstar: Double = 1.0e-5 * tle[1].substring(53, 59).toDouble() / - 10.0.pow(tle[1].substring(60, 61).toDouble()) + val bstar: Double = 1.0e-5 * tle[1].substring(53, 59).toDouble() / 10.0.pow( + tle[1].substring(60, 61).toDouble() + ) return OrbitalData(name, epoch, meanmo, eccn, incl, raan, argper, meanan, catnum, bstar) } catch (exception: Exception) { return null @@ -154,7 +151,7 @@ class DataParser(private val defaultDispatcher: CoroutineDispatcher) { var dayOfYear = dayOfMonth // If leap year increment Feb days if (((year / 4 == 0) && (year / 100 != 0)) || (year / 400 == 0)) daysArray[1]++ - for (i in 0 until month - 1) { dayOfYear += daysArray[i] } + for (i in 0 until month - 1) dayOfYear += daysArray[i] return dayOfYear } } diff --git a/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt b/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt index c498b0e3..d20e1fa2 100644 --- a/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/DataRepository.kt @@ -18,9 +18,10 @@ package com.rtbishop.look4sat.data import com.rtbishop.look4sat.domain.IDataRepository -import com.rtbishop.look4sat.domain.ISettingsSource +import com.rtbishop.look4sat.domain.ISettingsRepository import com.rtbishop.look4sat.model.DataState import com.rtbishop.look4sat.model.SatEntry +import com.rtbishop.look4sat.model.SatItem import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -34,7 +35,7 @@ class DataRepository( private val radioSource: IRadiosStorage, private val remoteSource: INetworkSource, private val repositoryScope: CoroutineScope, - private val settingsManager: ISettingsSource + private val settingsRepository: ISettingsRepository ) : IDataRepository { private val exceptionHandler = CoroutineExceptionHandler { _, exception -> @@ -48,10 +49,17 @@ class DataRepository( override fun getRadiosTotal() = radioSource.getRadiosTotal() + override fun getSatelliteTypes() = remoteSource.satelliteSourcesMap.keys.sorted() + override suspend fun getEntriesWithModes() = entrySource.getEntriesWithModes() override suspend fun getEntriesWithIds(ids: List) = entrySource.getEntriesWithIds(ids) + override suspend fun getEntriesWithSelection(): List { + val selectedIds = settingsRepository.loadEntriesSelection() + return getEntriesWithModes().onEach { it.isSelected = it.catnum in selectedIds } + } + override suspend fun getRadiosWithId(id: Int) = radioSource.getRadiosWithId(id) override fun updateFromFile(uri: String) { @@ -69,7 +77,7 @@ class DataRepository( _updateState.value = DataState.Loading repositoryScope.launch(exceptionHandler) { val importedEntries = mutableListOf() - val sourcesMap = settingsManager.sourcesMap + val sourcesMap = remoteSource.satelliteSourcesMap val jobsMap = sourcesMap.mapValues { async { remoteSource.getDataStream(it.value) } } jobsMap.mapValues { job -> job.value.await() }.forEach { entry -> entry.value?.let { stream -> @@ -78,7 +86,7 @@ class DataRepository( // parse tle stream val satellites = importSatellites(stream) val catnums = satellites.map { it.data.catnum } - settingsManager.saveSatType(type, catnums) + settingsRepository.saveSatType(type, catnums) importedEntries.addAll(satellites) } "McCants", "Classified" -> { @@ -86,7 +94,7 @@ class DataRepository( val unzipped = ZipInputStream(stream).apply { nextEntry } val satellites = importSatellites(unzipped) val catnums = satellites.map { it.data.catnum } - settingsManager.saveSatType(type, catnums) + settingsRepository.saveSatType(type, catnums) importedEntries.addAll(satellites) } else -> { @@ -94,7 +102,7 @@ class DataRepository( val parsed = dataParser.parseCSVStream(stream) val satellites = parsed.map { data -> SatEntry(data) } val catnums = satellites.map { it.data.catnum } - settingsManager.saveSatType(type, catnums) + settingsRepository.saveSatType(type, catnums) importedEntries.addAll(satellites) } } @@ -104,7 +112,7 @@ class DataRepository( setUpdateSuccessful() } repositoryScope.launch(exceptionHandler) { - remoteSource.getDataStream(remoteSource.radioApi)?.let { stream -> + remoteSource.getDataStream(remoteSource.radioSourceUrl)?.let { stream -> radioSource.insertRadios(dataParser.parseJSONStream(stream)) } } @@ -125,7 +133,7 @@ class DataRepository( } private fun setUpdateSuccessful(updateTime: Long = System.currentTimeMillis()) { - settingsManager.setLastUpdateTime(updateTime) + settingsRepository.setLastUpdateTime(updateTime) _updateState.value = DataState.Success(updateTime) } diff --git a/base/src/main/java/com/rtbishop/look4sat/data/INetworkSource.kt b/base/src/main/java/com/rtbishop/look4sat/data/INetworkSource.kt index 4f30ca68..043df8f6 100644 --- a/base/src/main/java/com/rtbishop/look4sat/data/INetworkSource.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/INetworkSource.kt @@ -21,7 +21,36 @@ import java.io.InputStream interface INetworkSource { - val radioApi: String get() = "https://db.satnogs.org/api/transmitters/?format=json&status=active" + val radioSourceUrl: String get() = "https://db.satnogs.org/api/transmitters/?format=json" + val satelliteSourcesMap: Map + get() = mapOf( + "All" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", + "Amsat" to "https://amsat.org/tle/current/nasabare.txt", + "Amateur" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv", + "Classified" to "https://www.prismnet.com/~mmccants/tles/classfd.zip", + "Cubesat" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=csv", + "Education" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=education&FORMAT=csv", + "Engineer" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=engineering&FORMAT=csv", + "Geostationary" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=geo&FORMAT=csv", + "Globalstar" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=globalstar&FORMAT=csv", + "GNSS" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=gnss&FORMAT=csv", + "Intelsat" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=intelsat&FORMAT=csv", + "Iridium" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=iridium-NEXT&FORMAT=csv", + "McCants" to "https://www.prismnet.com/~mmccants/tles/inttles.zip", + "Military" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=military&FORMAT=csv", + "New" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=last-30-days&FORMAT=csv", + "OneWeb" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=oneweb&FORMAT=csv", + "Orbcomm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=orbcomm&FORMAT=csv", + "R4UAB" to "https://r4uab.ru/satonline.txt", + "Resource" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=resource&FORMAT=csv", + "SatNOGS" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=satnogs&FORMAT=csv", + "Science" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=science&FORMAT=csv", + "Spire" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=spire&FORMAT=csv", + "Starlink" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=starlink&FORMAT=csv", + "Swarm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=swarm&FORMAT=csv", + "Weather" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=weather&FORMAT=csv", + "X-Comm" to "https://celestrak.com/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv" + ) suspend fun getDataStream(url: String): InputStream? } diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/SatelliteManager.kt b/base/src/main/java/com/rtbishop/look4sat/data/SatelliteRepository.kt similarity index 94% rename from base/src/main/java/com/rtbishop/look4sat/domain/SatelliteManager.kt rename to base/src/main/java/com/rtbishop/look4sat/data/SatelliteRepository.kt index 2b0fb687..824c82e2 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/SatelliteManager.kt +++ b/base/src/main/java/com/rtbishop/look4sat/data/SatelliteRepository.kt @@ -15,8 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.rtbishop.look4sat.domain +package com.rtbishop.look4sat.data +import com.rtbishop.look4sat.domain.ISatelliteRepository +import com.rtbishop.look4sat.domain.Satellite import com.rtbishop.look4sat.model.GeoPos import com.rtbishop.look4sat.model.SatPass import com.rtbishop.look4sat.model.SatPos @@ -28,7 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.withContext -class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISatelliteManager { +class SatelliteRepository(private val dispatcher: CoroutineDispatcher) : ISatelliteRepository { private var passes = listOf() private val _calculatedPasses = MutableSharedFlow>(replay = 1) @@ -37,13 +39,13 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa override fun getPasses(): List = passes override suspend fun getPosition(sat: Satellite, pos: GeoPos, time: Long): SatPos { - return withContext(defaultDispatcher) { sat.getPosition(pos, time) } + return withContext(dispatcher) { sat.getPosition(pos, time) } } override suspend fun getTrack( sat: Satellite, pos: GeoPos, start: Long, end: Long ): List { - return withContext(defaultDispatcher) { + return withContext(dispatcher) { val positions = mutableListOf() var currentTime = start while (currentTime < end) { @@ -57,7 +59,7 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa override suspend fun processRadios( sat: Satellite, pos: GeoPos, radios: List, time: Long ): List { - return withContext(defaultDispatcher) { + return withContext(dispatcher) { val satPos = sat.getPosition(pos, time) val copiedList = radios.map { it.copy() } copiedList.forEach { transmitter -> @@ -73,7 +75,7 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa } override suspend fun processPasses(passList: List, time: Long): List { - return withContext(defaultDispatcher) { + return withContext(dispatcher) { passList.forEach { pass -> if (!pass.isDeepSpace) { val timeStart = pass.aosTime @@ -96,7 +98,7 @@ class SatelliteManager(private val defaultDispatcher: CoroutineDispatcher) : ISa passes = newPasses _calculatedPasses.emit(newPasses) } else { - withContext(defaultDispatcher) { + withContext(dispatcher) { val allPasses = mutableListOf() satList.forEach { satellite -> allPasses.addAll(satellite.getPasses(pos, time, hoursAhead)) diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt b/base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSatellite.kt similarity index 99% rename from base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt rename to base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSatellite.kt index c6a40a85..8f4b9899 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSat.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/DeepSpaceSatellite.kt @@ -20,7 +20,7 @@ package com.rtbishop.look4sat.domain import com.rtbishop.look4sat.model.OrbitalData import kotlin.math.* -class DeepSpaceSat(data: OrbitalData) : Satellite(data) { +class DeepSpaceSatellite(data: OrbitalData) : Satellite(data) { private val c1: Double private val c4: Double diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt b/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt index 9cabd0de..520a5ce9 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/IDataRepository.kt @@ -31,10 +31,14 @@ interface IDataRepository { fun getRadiosTotal(): Flow + fun getSatelliteTypes(): List + suspend fun getEntriesWithModes(): List suspend fun getEntriesWithIds(ids: List): List + suspend fun getEntriesWithSelection(): List + suspend fun getRadiosWithId(id: Int): List fun updateFromFile(uri: String) diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/ILocationSource.kt b/base/src/main/java/com/rtbishop/look4sat/domain/ILocationSource.kt deleted file mode 100644 index b9891d42..00000000 --- a/base/src/main/java/com/rtbishop/look4sat/domain/ILocationSource.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Look4Sat. Amateur radio satellite tracker and pass predictor. - * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.rtbishop.look4sat.domain - -import com.rtbishop.look4sat.model.GeoPos -import kotlinx.coroutines.flow.StateFlow - -interface ILocationSource { - - val stationPosition: StateFlow - - fun setGpsPosition(): Boolean - - fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean - - fun setQthPosition(locator: String): Boolean -} diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteManager.kt b/base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteRepository.kt similarity index 96% rename from base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteManager.kt rename to base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteRepository.kt index 370b4335..844640ed 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteManager.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/ISatelliteRepository.kt @@ -6,7 +6,7 @@ import com.rtbishop.look4sat.model.SatPos import com.rtbishop.look4sat.model.SatRadio import kotlinx.coroutines.flow.SharedFlow -interface ISatelliteManager { +interface ISatelliteRepository { val calculatedPasses: SharedFlow> diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/ISensorRepository.kt b/base/src/main/java/com/rtbishop/look4sat/domain/ISensorRepository.kt new file mode 100644 index 00000000..0580adc3 --- /dev/null +++ b/base/src/main/java/com/rtbishop/look4sat/domain/ISensorRepository.kt @@ -0,0 +1,12 @@ +package com.rtbishop.look4sat.domain + +import kotlinx.coroutines.flow.StateFlow + +interface ISensorRepository { + + val orientation: StateFlow> + + fun enableSensor() + + fun disableSensor() +} diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsRepository.kt b/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsRepository.kt new file mode 100644 index 00000000..010940eb --- /dev/null +++ b/base/src/main/java/com/rtbishop/look4sat/domain/ISettingsRepository.kt @@ -0,0 +1,100 @@ +/* + * Look4Sat. Amateur radio satellite tracker and pass predictor. + * Copyright (C) 2019-2022 Arty Bishop (bishop.arty@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.rtbishop.look4sat.domain + +import com.rtbishop.look4sat.model.GeoPos +import kotlinx.coroutines.flow.StateFlow + +interface ISettingsRepository { + + val stationPosition: StateFlow + + fun setGpsPosition(): Boolean + + fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double = 0.0): Boolean + + fun setQthPosition(locator: String): Boolean + + fun getHoursAhead(): Int + + fun setHoursAhead(hoursAhead: Int) + + fun getMinElevation(): Double + + fun setMinElevation(minElevation: Double) + + fun isUtcEnabled(): Boolean + + fun setUtcState(value: Boolean) + + fun getLastUpdateTime(): Long + + fun setLastUpdateTime(updateTime: Long) + + fun isUpdateEnabled(): Boolean + + fun setUpdateState(value: Boolean) + + fun isSensorEnabled(): Boolean + + fun setSensorState(value: Boolean) + + fun isSweepEnabled(): Boolean + + fun setSweepState(value: Boolean) + + fun saveModesSelection(modes: List) + + fun loadModesSelection(): List + + fun saveEntriesSelection(catnums: List) + + fun loadEntriesSelection(): List + + fun saveSatType(type: String, catnums: List) + + fun loadSatType(type: String): List + + fun getRotatorEnabled(): Boolean + + fun setRotatorEnabled(value: Boolean) + + fun getRotatorServer(): String + + fun setRotatorServer(value: String) + + fun getRotatorPort(): String + + fun setRotatorPort(value: String) + + fun getBTEnabled(): Boolean + + fun setBTEnabled(value: Boolean) + + fun getBTDeviceAddr(): String + + fun setBTDeviceAddr(value: String) + + fun getBTDeviceName(): String + + fun setBTDeviceName(value: String) + + fun getBTFormat(): String + + fun setBTFormat(value: String) +} diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt b/base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSatellite.kt similarity index 99% rename from base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt rename to base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSatellite.kt index 9f16df3b..15610e29 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSat.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/NearEarthSatellite.kt @@ -20,7 +20,7 @@ package com.rtbishop.look4sat.domain import com.rtbishop.look4sat.model.OrbitalData import kotlin.math.* -class NearEarthSat(data: OrbitalData) : Satellite(data) { +class NearEarthSatellite(data: OrbitalData) : Satellite(data) { private val aodp: Double private val aycof: Double diff --git a/base/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt b/base/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt index af31cfe0..dc1f8283 100644 --- a/base/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt +++ b/base/src/main/java/com/rtbishop/look4sat/domain/Satellite.kt @@ -110,8 +110,8 @@ abstract class Satellite(val data: OrbitalData) { } private fun calculateSDP4orSGP4(tsince: Double) { - if (data.isDeepSpace) (this as DeepSpaceSat).calculateSDP4(tsince) - else (this as NearEarthSat).calculateSGP4(tsince) + if (data.isDeepSpace) (this as DeepSpaceSatellite).calculateSDP4(tsince) + else (this as NearEarthSatellite).calculateSGP4(tsince) } // Converts the sat position and velocity vectors to km and km/sec diff --git a/base/src/main/java/com/rtbishop/look4sat/model/OrbitalData.kt b/base/src/main/java/com/rtbishop/look4sat/model/OrbitalData.kt index 0b179633..c92528fb 100644 --- a/base/src/main/java/com/rtbishop/look4sat/model/OrbitalData.kt +++ b/base/src/main/java/com/rtbishop/look4sat/model/OrbitalData.kt @@ -39,5 +39,6 @@ data class OrbitalData( // Space objects are classified as NearEarth (period < 225 min) or DeepSpace (period >= 225 min) val isDeepSpace: Boolean = orbitalPeriod >= 225.0 ) { - fun getSatellite(): Satellite = if (isDeepSpace) DeepSpaceSat(this) else NearEarthSat(this) + fun getSatellite(): Satellite = + if (isDeepSpace) DeepSpaceSatellite(this) else NearEarthSatellite(this) }