Extracted station position management to a separate class

This commit is contained in:
Arty Bishop committed 2021-05-16 19:13:10 +01:00
1 parent 5ef79148bd
commit 1a59b5e3e1
31 files changed
+227 -496

No files matched your search

-5
View File
@@ -15,11 +15,6 @@ android {
targetSdkVersion 30
versionCode 240
versionName "2.4.0"
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}
buildTypes {
@@ -1,114 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "24170ecaf2ab85adb66aa3b557134997",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`catNum`))",
"fields": [
{
"fieldPath": "tle",
"columnName": "tle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catNum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `description` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `uplink` INTEGER, `downlink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "description",
"columnName": "description",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '24170ecaf2ab85adb66aa3b557134997')"
]
}
}
@@ -1,114 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "53fa623fc55b279c50d33e6263866a89",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`catNum`))",
"fields": [
{
"fieldPath": "tle",
"columnName": "tle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catNum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '53fa623fc55b279c50d33e6263866a89')"
]
}
}
@@ -1,114 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "53fa623fc55b279c50d33e6263866a89",
"entities": [
{
"tableName": "entries",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tle` TEXT NOT NULL, `catNum` INTEGER NOT NULL, `name` TEXT NOT NULL, `isSelected` INTEGER NOT NULL, PRIMARY KEY(`catNum`))",
"fields": [
{
"fieldPath": "tle",
"columnName": "tle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSelected",
"columnName": "isSelected",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"catNum"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "transmitters",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `info` TEXT NOT NULL, `isAlive` INTEGER NOT NULL, `downlink` INTEGER, `uplink` INTEGER, `mode` TEXT, `isInverted` INTEGER NOT NULL, `catNum` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
"fields": [
{
"fieldPath": "uuid",
"columnName": "uuid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "info",
"columnName": "info",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlive",
"columnName": "isAlive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downlink",
"columnName": "downlink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "uplink",
"columnName": "uplink",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "mode",
"columnName": "mode",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isInverted",
"columnName": "isInverted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "catNum",
"columnName": "catNum",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"uuid"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '53fa623fc55b279c50d33e6263866a89')"
]
}
}
@@ -18,11 +18,12 @@
package com.rtbishop.look4sat.di
import android.content.Context
import android.content.SharedPreferences
import android.location.LocationManager
import androidx.room.Room
import com.rtbishop.look4sat.data.DefaultSatelliteRepo
import com.rtbishop.look4sat.data.LocalDataSource
import com.rtbishop.look4sat.data.RemoteDataSource
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.*
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.framework.DefaultLocationSource
import com.rtbishop.look4sat.framework.api.NetworkDataSource
import com.rtbishop.look4sat.framework.api.SatelliteService
import com.rtbishop.look4sat.framework.db.RoomConverters
@@ -51,12 +52,30 @@ object DataSourceModule {
@Provides
@Singleton
fun provideDefaultRepository(
fun provideLocationSource(
locationManager: LocationManager,
preferences: SharedPreferences
): LocationSource {
return DefaultLocationSource(locationManager, preferences)
}
@Provides
@Singleton
fun provideLocationRepo(
locationSource: LocationSource,
qthConverter: QthConverter
): LocationRepo {
return LocationRepo(locationSource, qthConverter)
}
@Provides
@Singleton
fun provideSatelliteRepo(
localSource: LocalDataSource,
remoteSource: RemoteDataSource,
@IoDispatcher ioDispatcher: CoroutineDispatcher
): SatelliteRepo {
return DefaultSatelliteRepo(localSource, remoteSource, ioDispatcher)
return SatelliteRepo(localSource, remoteSource, ioDispatcher)
}
@Provides
@@ -17,7 +17,7 @@
*/
package com.rtbishop.look4sat.di
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.interactors.*
import dagger.Module
import dagger.Provides
@@ -0,0 +1,62 @@
package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.core.content.edit
import com.rtbishop.look4sat.data.LocationSource
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.utility.round
import javax.inject.Inject
class DefaultLocationSource @Inject constructor(
private val locationManager: LocationManager,
private val preferences: SharedPreferences
) : LocationSource {
companion object {
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyAltitude = "stationAlt"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
override fun getLastKnownLocation(): StationPosition? {
return try {
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
if (location == null) null
else {
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1)
return StationPosition(latitude, longitude, altitude)
}
} catch (exception: SecurityException) {
null
}
}
override fun getMagDeclination(position: StationPosition): Float {
val lat = position.latitude.toFloat()
val lon = position.longitude.toFloat()
val alt = position.altitude.toFloat()
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
}
override fun loadStationPosition(): StationPosition {
val defaultSP = "0.0"
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
val altitude = preferences.getString(keyAltitude, null) ?: defaultSP
return StationPosition(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
}
override fun saveStationPosition(position: StationPosition) {
preferences.edit {
putString(keyLatitude, position.latitude.toString())
putString(keyLongitude, position.longitude.toString())
putString(keyAltitude, position.altitude.toString())
}
}
}
@@ -15,7 +15,7 @@
* 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.utility
package com.rtbishop.look4sat.framework
import android.hardware.Sensor
import android.hardware.SensorEvent
@@ -15,20 +15,18 @@
* 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.utility
package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import android.hardware.GeomagneticField
import androidx.core.content.edit
import com.rtbishop.look4sat.framework.model.TleSource
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PrefsManager @Inject constructor(val preferences: SharedPreferences, moshi: Moshi) {
class PrefsManager @Inject constructor(private val preferences: SharedPreferences, moshi: Moshi) {
private val sourcesType = Types.newParameterizedType(List::class.java, TleSource::class.java)
private val sourcesAdapter = moshi.adapter<List<TleSource>>(sourcesType)
@@ -36,16 +34,11 @@ class PrefsManager @Inject constructor(val preferences: SharedPreferences, moshi
companion object {
const val keySources = "sourcesListJson"
const val keyModes = "satModes"
const val keyLatitude = "latitude"
const val keyLongitude = "longitude"
const val keyAltitude = "altitude"
const val keyCompass = "compass"
const val keyTextLabels = "shouldUseTextLabels"
const val keyTimeUTC = "timeUTC"
const val keyHoursAhead = "hoursAhead"
const val keyMinElevation = "minElevation"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
const val keyIsFirstLaunch = "shouldShowSplash"
const val keyRotator = "isRotatorEnabled"
const val keyRotatorAddress = "rotatorAddress"
@@ -60,30 +53,6 @@ class PrefsManager @Inject constructor(val preferences: SharedPreferences, moshi
return preferences.getInt(keyMinElevation, 16).toDouble()
}
fun getStationPosition(): StationPosition {
val defaultGSP = "0.0"
val latitude = preferences.getString(keyLatitude, null) ?: defaultGSP
val longitude = preferences.getString(keyLongitude, null) ?: defaultGSP
val altitude = preferences.getString(keyAltitude, null) ?: defaultGSP
return StationPosition(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
}
fun setStationPosition(latitude: Double, longitude: Double, altitude: Double) {
preferences.edit {
putString(keyLatitude, latitude.toString())
putString(keyLongitude, longitude.toString())
putString(keyAltitude, altitude.toString())
}
}
fun getMagDeclination(): Float {
val stationPosition = getStationPosition()
val lat = stationPosition.latitude.toFloat()
val lon = stationPosition.longitude.toFloat()
val alt = stationPosition.altitude.toFloat()
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
}
fun shouldUseTextLabels(): Boolean {
return preferences.getBoolean(keyTextLabels, false)
}
@@ -23,7 +23,7 @@ import androidx.room.TypeConverters
import com.rtbishop.look4sat.framework.model.SatEntry
import com.rtbishop.look4sat.framework.model.SatTrans
@Database(entities = [SatEntry::class, SatTrans::class], version = 1)
@Database(entities = [SatEntry::class, SatTrans::class], version = 1, exportSchema = false)
@TypeConverters(RoomConverters::class)
abstract class SatelliteDb : RoomDatabase() {
@@ -17,6 +17,7 @@
*/
package com.rtbishop.look4sat.presentation
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
@@ -24,7 +25,8 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.ActivityMainBinding
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import com.rtbishop.look4sat.utility.navigateSafe
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -34,6 +36,7 @@ class Look4SatActivity : AppCompatActivity() {
@Inject
lateinit var prefsManager: PrefsManager
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setTheme(R.style.AppTheme)
@@ -44,7 +47,7 @@ class Look4SatActivity : AppCompatActivity() {
binding.navBottom.setupWithNavController(navHost.navController)
if (prefsManager.isFirstLaunch()) {
prefsManager.setFirstLaunchDone()
navHost.navController.navigate(R.id.nav_dialog_splash)
navHost.navController.navigateSafe(R.id.nav_dialog_splash)
}
}
}
@@ -32,7 +32,7 @@ import com.rtbishop.look4sat.interactors.GetSatItems
import com.rtbishop.look4sat.interactors.ImportDataFromStream
import com.rtbishop.look4sat.interactors.ImportDataFromWeb
import com.rtbishop.look4sat.interactors.UpdateEntriesSelection
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -156,8 +156,8 @@ class EntriesViewModel @Inject constructor(
items.filter { it.catNum == query.toInt() }
} catch (e: Exception) {
items.filter { item ->
val itemName = item.name.toLowerCase(Locale.getDefault())
itemName.contains(query.toLowerCase(Locale.getDefault()))
val itemName = item.name.lowercase(Locale.getDefault())
itemName.contains(query.lowercase(Locale.getDefault()))
}
}
}
@@ -17,6 +17,7 @@
*/
package com.rtbishop.look4sat.presentation.mapScreen
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
@@ -40,10 +41,14 @@ import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.*
import timber.log.Timber
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class MapFragment : Fragment(R.layout.fragment_map) {
@Inject
lateinit var sharedPreferences: SharedPreferences
private val viewModel: MapViewModel by viewModels()
private val minLat = MapView.getTileSystem().minLatitude
private val maxLat = MapView.getTileSystem().maxLatitude
@@ -63,7 +68,7 @@ class MapFragment : Fragment(R.layout.fragment_map) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Configuration.getInstance().load(requireContext(), viewModel.getPreferences())
Configuration.getInstance().load(requireContext(), sharedPreferences)
val binding = FragmentMapBinding.bind(view).apply {
mapView.apply {
setMultiTouchControls(true)
@@ -183,7 +188,8 @@ class MapFragment : Fragment(R.layout.fragment_map) {
private fun renderSatData(satData: SatData, binding: FragmentMapBinding) {
binding.apply {
idName.text = String.format(getString(R.string.pat_osm_idName), satData.catNum, satData.name)
idName.text =
String.format(getString(R.string.pat_osm_idName), satData.catNum, satData.name)
qthLocator.text = String.format(getString(R.string.map_qth), satData.qthLoc)
altitude.text = String.format(getString(R.string.pat_altitude), satData.altitude)
distance.text = String.format(getString(R.string.pat_distance), satData.range)
@@ -17,16 +17,15 @@
*/
package com.rtbishop.look4sat.presentation.mapScreen
import android.content.SharedPreferences
import androidx.lifecycle.*
import com.rtbishop.look4sat.data.LocationRepo
import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.domain.predict4kotlin.Position
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.framework.model.SatData
import com.rtbishop.look4sat.interactors.GetSelectedSatellites
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import java.util.*
@@ -40,11 +39,11 @@ import kotlin.math.sqrt
class MapViewModel @Inject constructor(
private val getSelectedSatellites: GetSelectedSatellites,
private val prefsManager: PrefsManager,
private val qthConverter: QthConverter,
private val locationRepo: LocationRepo,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() {
private val gsp = prefsManager.getStationPosition()
private val gsp = locationRepo.getStationPosition()
private var dataUpdateJob: Job? = null
private var allSatList = listOf<Satellite>()
private lateinit var selectedSat: Satellite
@@ -78,10 +77,6 @@ class MapViewModel @Inject constructor(
}
}
fun getPreferences(): SharedPreferences {
return prefsManager.preferences
}
fun shouldUseTextLabels(): Boolean {
return prefsManager.shouldUseTextLabels()
}
@@ -177,7 +172,7 @@ class MapViewModel @Inject constructor(
val osmLat = getOsmLat(Math.toDegrees(satPos.latitude))
val osmLon = getOsmLon(Math.toDegrees(satPos.longitude))
val osmPos = Position(osmLat, osmLon)
val qthLoc = qthConverter.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
val qthLoc = locationRepo.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
val velocity = getOrbitalVelocity(satPos.altitude)
val satData = SatData(
sat, sat.tle.catnum, sat.tle.name, satPos.range,
@@ -25,7 +25,7 @@ import com.rtbishop.look4sat.framework.model.Result
import com.rtbishop.look4sat.utility.PassesRepo
import com.rtbishop.look4sat.interactors.GetSelectedSatellites
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
@@ -18,13 +18,14 @@
package com.rtbishop.look4sat.presentation.polarScreen
import androidx.lifecycle.*
import com.rtbishop.look4sat.data.LocationRepo
import com.rtbishop.look4sat.di.IoDispatcher
import com.rtbishop.look4sat.domain.model.SatTrans
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.interactors.GetTransmittersForSat
import com.rtbishop.look4sat.utility.OrientationProvider
import com.rtbishop.look4sat.framework.OrientationProvider
import com.rtbishop.look4sat.utility.PassesRepo
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import com.rtbishop.look4sat.utility.round
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
@@ -36,6 +37,7 @@ import javax.inject.Inject
@HiltViewModel
class PolarViewModel @Inject constructor(
private val locationRepo: LocationRepo,
private val orientationProvider: OrientationProvider,
private val prefsManager: PrefsManager,
private val passesRepo: PassesRepo,
@@ -68,7 +70,7 @@ class PolarViewModel @Inject constructor(
}
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
_orientation.value = Triple(azimuth + prefsManager.getMagDeclination(), pitch, roll)
_orientation.value = Triple(azimuth + locationRepo.getMagDeclination(), pitch, roll)
}
private fun initRotatorControl(satPass: SatPass) {
@@ -19,7 +19,6 @@ package com.rtbishop.look4sat.presentation.prefsScreen
import android.Manifest
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Bundle
import android.text.InputType
import android.util.Patterns
@@ -31,9 +30,9 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.utility.round
import com.rtbishop.look4sat.data.LocationRepo
import com.rtbishop.look4sat.framework.DefaultLocationSource
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -41,16 +40,15 @@ import javax.inject.Inject
class PrefsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var locationManager: LocationManager
lateinit var locationRepo: LocationRepo
@Inject
lateinit var prefsManager: PrefsManager
@Inject
lateinit var qthConverter: QthConverter
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
setPositionFromGPS()
updatePositionFromGPS()
} else {
showSnack(getString(R.string.pref_pos_gps_error))
}
@@ -63,16 +61,16 @@ class PrefsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(PrefsManager.keyPositionGPS)?.apply {
findPreference<Preference>(DefaultLocationSource.keyPositionGPS)?.apply {
setOnPreferenceClickListener {
setPositionFromGPS()
updatePositionFromGPS()
return@setOnPreferenceClickListener true
}
}
findPreference<Preference>(PrefsManager.keyPositionQTH)?.apply {
findPreference<Preference>(DefaultLocationSource.keyPositionQTH)?.apply {
setOnPreferenceChangeListener { _, newValue ->
setPositionFromQth(newValue.toString())
updatePositionFromQth(newValue.toString())
}
}
@@ -101,34 +99,29 @@ class PrefsFragment : PreferenceFragmentCompat() {
}
}
private fun setPositionFromQth(qthString: String): Boolean {
qthConverter.qthToPosition(qthString)?.let { gsp ->
prefsManager.setStationPosition(gsp.latitude, gsp.longitude, 0.0)
private fun updatePositionFromQth(qthString: String): Boolean {
return if (locationRepo.updatePositionFromQTH(qthString)) {
showSnack(getString(R.string.pref_pos_success))
return true
}
true
} else {
showSnack(getString(R.string.pref_pos_qth_error))
return false
false
}
}
private fun setPositionFromGPS() {
private fun updatePositionFromGPS() {
val locPermString = Manifest.permission.ACCESS_FINE_LOCATION
val locPermResult = ContextCompat.checkSelfPermission(requireContext(), locPermString)
if (locPermResult == PackageManager.PERMISSION_GRANTED) {
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
if (location != null) {
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1)
prefsManager.setStationPosition(latitude, longitude, altitude)
if (locationRepo.updatePositionFromGPS()) {
showSnack(getString(R.string.pref_pos_success))
} else showSnack(getString(R.string.pref_pos_gps_null))
} else requestPermissionLauncher.launch(locPermString)
}
private fun showSnack(message: String) {
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT)
.setAnchorView(R.id.nav_bottom)
.show()
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).apply {
setAnchorView(R.id.nav_bottom)
}.show()
}
}
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.utility.PrefsManager
import com.rtbishop.look4sat.framework.PrefsManager
import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.utility.setNavResult
import dagger.hilt.android.AndroidEntryPoint
@@ -17,9 +17,11 @@
*/
package com.rtbishop.look4sat.utility
import com.rtbishop.look4sat.data.LocationRepo
import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import com.rtbishop.look4sat.framework.PrefsManager
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -30,6 +32,7 @@ import javax.inject.Singleton
@Singleton
class PassesRepo @Inject constructor(
private val locationRepo: LocationRepo,
private val prefsManager: PrefsManager,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) {
@@ -61,7 +64,7 @@ class PassesRepo @Inject constructor(
}
private fun getPasses(satellite: Satellite, refDate: Date): List<SatPass> {
val predictor = satellite.getPredictor(prefsManager.getStationPosition())
val predictor = satellite.getPredictor(locationRepo.getStationPosition())
return predictor.getPasses(refDate, prefsManager.getHoursAhead(), true)
}
@@ -0,0 +1,35 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
class LocationRepo(
private val locationSource: LocationSource,
private val qthConverter: QthConverter
) {
fun getStationPosition(): StationPosition {
return locationSource.loadStationPosition()
}
fun getMagDeclination(): Float {
return locationSource.getMagDeclination(getStationPosition())
}
fun positionToQTH(lat: Double, lon: Double): String? {
return qthConverter.positionToQTH(lat, lon)
}
fun updatePositionFromGPS(): Boolean {
val stationPosition = locationSource.getLastKnownLocation() ?: return false
locationSource.saveStationPosition(stationPosition)
return true
}
fun updatePositionFromQTH(qthString: String): Boolean {
val position = qthConverter.qthToPosition(qthString) ?: return false
val stationPosition = StationPosition(position.latitude, position.longitude, 0.0)
locationSource.saveStationPosition(stationPosition)
return true
}
}
@@ -0,0 +1,14 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
interface LocationSource {
fun getLastKnownLocation(): StationPosition?
fun getMagDeclination(position: StationPosition): Float
fun loadStationPosition(): StationPosition
fun saveStationPosition(position: StationPosition)
}
@@ -1,6 +1,5 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.domain.model.SatEntry
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.model.SatTrans
@@ -13,30 +12,30 @@ import kotlinx.coroutines.withContext
import java.io.InputStream
import java.util.zip.ZipInputStream
class DefaultSatelliteRepo(
class SatelliteRepo(
private val localSource: LocalDataSource,
private val remoteSource: RemoteDataSource,
private val ioDispatcher: CoroutineDispatcher
) : SatelliteRepo {
) {
override fun getSatItems(): Flow<List<SatItem>> {
fun getSatItems(): Flow<List<SatItem>> {
return localSource.getSatItems()
}
override fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>> {
fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>> {
return localSource.getTransmittersForSat(catNum)
}
override suspend fun getSelectedSatellites(): List<Satellite> {
suspend fun getSelectedSatellites(): List<Satellite> {
return localSource.getSelectedSatellites()
}
override suspend fun importDataFromStream(stream: InputStream) = withContext(ioDispatcher) {
suspend fun importDataFromStream(stream: InputStream) = withContext(ioDispatcher) {
val importedEntries = Satellite.importElements(stream).map { tle -> SatEntry(tle) }
localSource.updateEntries(importedEntries)
}
override suspend fun importDataFromWeb(sources: List<String>) {
suspend fun importDataFromWeb(sources: List<String>) {
coroutineScope {
launch(ioDispatcher) {
val importedEntries = mutableListOf<SatEntry>()
@@ -64,7 +63,7 @@ class DefaultSatelliteRepo(
}
}
override suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
localSource.updateEntriesSelection(catNums, isSelected)
}
}
@@ -1,22 +0,0 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.model.SatTrans
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import kotlinx.coroutines.flow.Flow
import java.io.InputStream
interface SatelliteRepo {
fun getSatItems(): Flow<List<SatItem>>
fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>>
suspend fun getSelectedSatellites(): List<Satellite>
suspend fun importDataFromStream(stream: InputStream)
suspend fun importDataFromWeb(sources: List<String>)
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean)
}
@@ -24,12 +24,12 @@ class QthConverter {
fun qthToPosition(qthString: String): Position? {
val trimmedQth = qthString.take(6)
if (!isValidQTH(trimmedQth)) return null
val lonFirst = (trimmedQth[0].toUpperCase().toInt() - 65) * 20
val latFirst = (trimmedQth[1].toUpperCase().toInt() - 65) * 10
val lonFirst = (trimmedQth[0].uppercaseChar().code - 65) * 20
val latFirst = (trimmedQth[1].uppercaseChar().code - 65) * 10
val lonSecond = trimmedQth[2].toString().toInt() * 2
val latSecond = trimmedQth[3].toString().toInt()
val lonThird = (((trimmedQth[4].toLowerCase().toInt() - 97) / 12.0) + (1.0 / 24.0)) - 180
val latThird = (((trimmedQth[5].toLowerCase().toInt() - 97) / 24.0) + (1.0 / 48.0)) - 90
val lonThird = (((trimmedQth[4].lowercaseChar().code - 97) / 12.0) + (1.0 / 24.0)) - 180
val latThird = (((trimmedQth[5].lowercaseChar().code - 97) / 24.0) + (1.0 / 48.0)) - 90
val longitude = (lonFirst + lonSecond + lonThird).roundToDecimals(4)
val latitude = (latFirst + latSecond + latThird).roundToDecimals(4)
return Position(latitude, longitude)
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.domain.model.SatItem
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
class GetSelectedSatellites(private val satelliteRepo: SatelliteRepo) {
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.domain.model.SatTrans
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
import java.io.InputStream
class ImportDataFromStream(private val satelliteRepo: SatelliteRepo) {
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
class ImportDataFromWeb(private val satelliteRepo: SatelliteRepo) {
@@ -1,6 +1,6 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.domain.SatelliteRepo
import com.rtbishop.look4sat.data.SatelliteRepo
class UpdateEntriesSelection(private val satelliteRepo: SatelliteRepo) {
@@ -22,39 +22,39 @@ import org.junit.Test
class QthConverterTest {
private val converter = QthConverter()
private val qthConverter = QthConverter()
@Test
fun `Given valid QTH returns correct location`() {
var result = converter.qthToPosition("io91VL39FX")
fun `Given valid QTH returns correct POS`() {
var result = qthConverter.qthToPosition("io91VL39FX")
assert(result?.latitude == 51.4792 && result.longitude == -0.2083)
result = converter.qthToPosition("JN58TD")
result = qthConverter.qthToPosition("JN58TD")
assert(result?.latitude == 48.1458 && result.longitude == 11.6250)
result = converter.qthToPosition("gf15vc")
result = qthConverter.qthToPosition("gf15vc")
assert(result?.latitude == -34.8958 && result.longitude == -56.2083)
result = converter.qthToPosition("fm18LW")
result = qthConverter.qthToPosition("fm18LW")
assert(result?.latitude == 38.9375 && result.longitude == -77.0417)
}
@Test
fun `Given invalid QTH returns null`() {
var result = converter.qthToPosition("ZZ00zz")
var result = qthConverter.qthToPosition("ZZ00zz")
assert(result == null)
result = converter.qthToPosition("JN58tz")
result = qthConverter.qthToPosition("JN58tz")
assert(result == null)
}
@Test
fun `Given valid location returns correct QTH`() {
assert(converter.positionToQTH(51.4878, -0.2146) == "IO91vl")
assert(converter.positionToQTH(48.1466, 11.6083) == "JN58td")
assert(converter.positionToQTH(-34.91, -56.2116) == "GF15vc")
assert(converter.positionToQTH(38.92, -77.065) == "FM18lw")
fun `Given valid POS returns correct QTH`() {
assert(qthConverter.positionToQTH(51.4878, -0.2146) == "IO91vl")
assert(qthConverter.positionToQTH(48.1466, 11.6083) == "JN58td")
assert(qthConverter.positionToQTH(-34.91, -56.2116) == "GF15vc")
assert(qthConverter.positionToQTH(38.92, -77.065) == "FM18lw")
}
@Test
fun `Given invalid location returns null`() {
assert(converter.positionToQTH(91.0542, -170.1142) == null)
assert(converter.positionToQTH(89.0542, -240.1142) == null)
assert(qthConverter.positionToQTH(91.0542, -170.1142) == null)
assert(qthConverter.positionToQTH(89.0542, -240.1142) == null)
}
}