mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Moved sources urls to database storage
This commit is contained in:
1 parent
af7e027350
commit
5f4c63ee49
27 files changed
+418
-137
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"identityHash": "825f728737780384e51a6a73221288c4",
|
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "entries",
|
"tableName": "entries",
|
||||||
@@ -103,12 +103,32 @@
|
|||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "sources",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceUrl",
|
||||||
|
"columnName": "sourceUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"sourceUrl"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '825f728737780384e51a6a73221288c4')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
|
||||||
|
"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, 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": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uuid"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "sources",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, PRIMARY KEY(`sourceUrl`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceUrl",
|
||||||
|
"columnName": "sourceUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"sourceUrl"
|
||||||
|
],
|
||||||
|
"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, '555494c464b5a29285eb5493cc8aa5f6')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.rtbishop.look4sat.framework
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.hardware.GeomagneticField
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationListener
|
||||||
|
import android.location.LocationManager
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.rtbishop.look4sat.domain.LocationProvider
|
||||||
|
import com.rtbishop.look4sat.domain.QthConverter
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import com.rtbishop.look4sat.utility.round
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class LocationSource @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val preferences: SharedPreferences
|
||||||
|
) : LocationListener, LocationProvider {
|
||||||
|
|
||||||
|
private val locManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
private val _updatedLocation = MutableSharedFlow<GeoPos?>()
|
||||||
|
override val updatedLocation: SharedFlow<GeoPos?> = _updatedLocation
|
||||||
|
|
||||||
|
fun getMagDeclination(stationPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
||||||
|
val lat = stationPos.latitude.toFloat()
|
||||||
|
val lon = stationPos.longitude.toFloat()
|
||||||
|
return GeomagneticField(lat, lon, 0f, time).declination
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePosition(latitude: Double, longitude: Double) {
|
||||||
|
if (QthConverter.isValidPosition(latitude, longitude)) {
|
||||||
|
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
|
||||||
|
} else _updatedLocation.tryEmit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePositionFromQth(qthString: String) {
|
||||||
|
val position = QthConverter.qthToPosition(qthString)
|
||||||
|
if (position != null) {
|
||||||
|
_updatedLocation.tryEmit(GeoPos(position.latitude, position.longitude))
|
||||||
|
} else _updatedLocation.tryEmit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePositionFromGps() {
|
||||||
|
val provider = LocationManager.GPS_PROVIDER
|
||||||
|
val permission = Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
val result = ContextCompat.checkSelfPermission(context, permission)
|
||||||
|
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
locManager.requestLocationUpdates(provider, 0L, 0f, this)
|
||||||
|
} else _updatedLocation.tryEmit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePositionFromNetwork() {
|
||||||
|
val provider = LocationManager.NETWORK_PROVIDER
|
||||||
|
val permission = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
val result = ContextCompat.checkSelfPermission(context, permission)
|
||||||
|
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
locManager.requestLocationUpdates(provider, 0L, 0f, this)
|
||||||
|
} else _updatedLocation.tryEmit(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
locManager.removeUpdates(this)
|
||||||
|
val latitude = location.latitude.round(4)
|
||||||
|
val longitude = location.longitude.round(4)
|
||||||
|
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,10 @@ import android.hardware.SensorEvent
|
|||||||
import android.hardware.SensorEventListener
|
import android.hardware.SensorEventListener
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class OrientationSource @Inject constructor(private val sensorManager: SensorManager) :
|
class OrientationSource @Inject constructor(private val sensorManager: SensorManager) :
|
||||||
SensorEventListener {
|
SensorEventListener {
|
||||||
|
|
||||||
|
|||||||
@@ -21,50 +21,19 @@ import android.content.SharedPreferences
|
|||||||
import android.hardware.GeomagneticField
|
import android.hardware.GeomagneticField
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.rtbishop.look4sat.domain.Constants
|
|
||||||
import com.rtbishop.look4sat.domain.predict.GeoPos
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.domain.QthConverter
|
import com.rtbishop.look4sat.domain.QthConverter
|
||||||
import com.rtbishop.look4sat.utility.round
|
import com.rtbishop.look4sat.utility.round
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.squareup.moshi.Types
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class PreferencesSource @Inject constructor(
|
class PreferencesSource @Inject constructor(
|
||||||
moshi: Moshi,
|
|
||||||
private val locationManager: LocationManager,
|
private val locationManager: LocationManager,
|
||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val sourcesType = Types.newParameterizedType(List::class.java, String::class.java)
|
|
||||||
private val sourcesAdapter = moshi.adapter<List<String>>(sourcesType)
|
|
||||||
|
|
||||||
fun loadTleSources(): List<String> {
|
|
||||||
return try {
|
|
||||||
val sourcesString = preferences.getString(keySources, String())
|
|
||||||
if (sourcesString.isNullOrEmpty()) {
|
|
||||||
loadDefaultSources()
|
|
||||||
} else {
|
|
||||||
sourcesAdapter.fromJson(sourcesString) ?: loadDefaultSources()
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
loadDefaultSources()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveTleSources(sources: List<String>) {
|
|
||||||
val sourcesJson = sourcesAdapter.toJson(sources)
|
|
||||||
preferences.edit { putString(keySources, sourcesJson) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadDefaultSources(): List<String> {
|
|
||||||
return listOf(
|
|
||||||
Constants.URL_CELESTRAK, Constants.URL_AMSAT,
|
|
||||||
Constants.URL_PRISM_CLASSFD, Constants.URL_PRISM_INTEL
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val keySources = "prefTleSourcesKey"
|
|
||||||
const val keyModes = "satModes"
|
const val keyModes = "satModes"
|
||||||
const val keyCompass = "compass"
|
const val keyCompass = "compass"
|
||||||
const val keyRadarSweep = "radarSweep"
|
const val keyRadarSweep = "radarSweep"
|
||||||
@@ -78,7 +47,6 @@ class PreferencesSource @Inject constructor(
|
|||||||
const val keyRotatorPort = "rotatorPort"
|
const val keyRotatorPort = "rotatorPort"
|
||||||
const val keyLatitude = "stationLat"
|
const val keyLatitude = "stationLat"
|
||||||
const val keyLongitude = "stationLon"
|
const val keyLongitude = "stationLon"
|
||||||
const val keyAltitude = "stationAlt"
|
|
||||||
const val keyPositionGPS = "setPositionGPS"
|
const val keyPositionGPS = "setPositionGPS"
|
||||||
const val keyPositionQTH = "setPositionQTH"
|
const val keyPositionQTH = "setPositionQTH"
|
||||||
}
|
}
|
||||||
@@ -87,15 +55,13 @@ class PreferencesSource @Inject constructor(
|
|||||||
val defaultSP = "0.0"
|
val defaultSP = "0.0"
|
||||||
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
|
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
|
||||||
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
|
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
|
||||||
val altitude = preferences.getString(keyAltitude, null) ?: defaultSP
|
return GeoPos(latitude.toDouble(), longitude.toDouble())
|
||||||
return GeoPos(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveStationPosition(pos: GeoPos) {
|
fun saveStationPosition(pos: GeoPos) {
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putString(keyLatitude, pos.latitude.toString())
|
putString(keyLatitude, pos.latitude.toString())
|
||||||
putString(keyLongitude, pos.longitude.toString())
|
putString(keyLongitude, pos.longitude.toString())
|
||||||
putString(keyAltitude, pos.altitude.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +72,7 @@ class PreferencesSource @Inject constructor(
|
|||||||
else {
|
else {
|
||||||
val latitude = location.latitude.round(4)
|
val latitude = location.latitude.round(4)
|
||||||
val longitude = location.longitude.round(4)
|
val longitude = location.longitude.round(4)
|
||||||
val altitude = location.altitude.round(1)
|
val stationPosition = GeoPos(latitude, longitude)
|
||||||
val stationPosition = GeoPos(latitude, longitude, altitude)
|
|
||||||
saveStationPosition(stationPosition)
|
saveStationPosition(stationPosition)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -118,7 +83,7 @@ class PreferencesSource @Inject constructor(
|
|||||||
|
|
||||||
fun updatePositionFromQTH(qthString: String): Boolean {
|
fun updatePositionFromQTH(qthString: String): Boolean {
|
||||||
val position = QthConverter.qthToPosition(qthString) ?: return false
|
val position = QthConverter.qthToPosition(qthString) ?: return false
|
||||||
val stationPosition = GeoPos(position.latitude, position.longitude, 0.0)
|
val stationPosition = GeoPos(position.latitude, position.longitude)
|
||||||
saveStationPosition(stationPosition)
|
saveStationPosition(stationPosition)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -127,8 +92,7 @@ class PreferencesSource @Inject constructor(
|
|||||||
val stationPosition = loadStationPosition()
|
val stationPosition = loadStationPosition()
|
||||||
val lat = stationPosition.latitude.toFloat()
|
val lat = stationPosition.latitude.toFloat()
|
||||||
val lon = stationPosition.longitude.toFloat()
|
val lon = stationPosition.longitude.toFloat()
|
||||||
val alt = stationPosition.altitude.toFloat()
|
return GeomagneticField(lat, lon, 0f, System.currentTimeMillis()).declination
|
||||||
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getHoursAhead(): Int {
|
fun getHoursAhead(): Int {
|
||||||
|
|||||||
@@ -23,16 +23,24 @@ import com.rtbishop.look4sat.domain.model.SatItem
|
|||||||
import com.rtbishop.look4sat.domain.predict.Satellite
|
import com.rtbishop.look4sat.domain.predict.Satellite
|
||||||
import com.rtbishop.look4sat.domain.model.Transmitter
|
import com.rtbishop.look4sat.domain.model.Transmitter
|
||||||
import com.rtbishop.look4sat.framework.DataMapper
|
import com.rtbishop.look4sat.framework.DataMapper
|
||||||
|
import com.rtbishop.look4sat.framework.model.DataSource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class LocalSource(private val satelliteDao: SatelliteDao) : LocalDataSource {
|
class LocalSource(
|
||||||
|
private val satelliteDao: SatelliteDao,
|
||||||
|
private val sourcesDao: SourcesDao
|
||||||
|
) : LocalDataSource {
|
||||||
|
|
||||||
override fun getEntriesWithModes(): Flow<List<SatItem>> {
|
override fun getEntriesWithModes(): Flow<List<SatItem>> {
|
||||||
return satelliteDao.getSatItems()
|
return satelliteDao.getSatItems()
|
||||||
.map { satItems -> DataMapper.satItemsToDomainItems(satItems) }
|
.map { satItems -> DataMapper.satItemsToDomainItems(satItems) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getSources(): List<String> {
|
||||||
|
return sourcesDao.getSources()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getSelectedSatellites(): List<Satellite> {
|
override suspend fun getSelectedSatellites(): List<Satellite> {
|
||||||
return satelliteDao.getSelectedSatellites()
|
return satelliteDao.getSelectedSatellites()
|
||||||
}
|
}
|
||||||
@@ -46,6 +54,11 @@ class LocalSource(private val satelliteDao: SatelliteDao) : LocalDataSource {
|
|||||||
satelliteDao.updateEntriesSelection(catNums, isSelected)
|
satelliteDao.updateEntriesSelection(catNums, isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun updateSources(sources: List<String>) {
|
||||||
|
sourcesDao.deleteSources()
|
||||||
|
sourcesDao.setSources(sources.map { DataSource(it) })
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTransmitters(catNum: Int): Flow<List<Transmitter>> {
|
override fun getTransmitters(catNum: Int): Flow<List<Transmitter>> {
|
||||||
return satelliteDao.getSatTransmitters(catNum)
|
return satelliteDao.getSatTransmitters(catNum)
|
||||||
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }
|
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }
|
||||||
|
|||||||
@@ -22,14 +22,21 @@ import androidx.room.RoomDatabase
|
|||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.migration.Migration
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import com.rtbishop.look4sat.framework.model.DataSource
|
||||||
import com.rtbishop.look4sat.framework.model.SatEntry
|
import com.rtbishop.look4sat.framework.model.SatEntry
|
||||||
import com.rtbishop.look4sat.framework.model.Transmitter
|
import com.rtbishop.look4sat.framework.model.Transmitter
|
||||||
|
|
||||||
@Database(entities = [SatEntry::class, Transmitter::class], version = 2, exportSchema = true)
|
@Database(
|
||||||
|
entities = [SatEntry::class, Transmitter::class, DataSource::class],
|
||||||
|
version = 3,
|
||||||
|
exportSchema = true
|
||||||
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class SatelliteDb : RoomDatabase() {
|
abstract class SatelliteDb : RoomDatabase() {
|
||||||
|
|
||||||
abstract fun satelliteDao(): SatelliteDao
|
abstract fun satelliteDao(): SatelliteDao
|
||||||
|
|
||||||
|
abstract fun sourcesDao(): SourcesDao
|
||||||
}
|
}
|
||||||
|
|
||||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||||
@@ -40,3 +47,9 @@ val MIGRATION_1_2 = object : Migration(1, 2) {
|
|||||||
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
|
database.execSQL("ALTER TABLE trans_backup RENAME TO transmitters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("CREATE TABLE sources (sourceUrl TEXT NOT NULL, PRIMARY KEY(sourceUrl))")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.rtbishop.look4sat.framework.local
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.rtbishop.look4sat.framework.model.DataSource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface SourcesDao {
|
||||||
|
|
||||||
|
@Query("SELECT sourceUrl FROM sources")
|
||||||
|
suspend fun getSources(): List<String>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun setSources(sources: List<DataSource>)
|
||||||
|
|
||||||
|
@Query("DELETE from sources")
|
||||||
|
suspend fun deleteSources()
|
||||||
|
}
|
||||||
+6
-2
@@ -15,6 +15,10 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.presentation.sourcesScreen
|
package com.rtbishop.look4sat.framework.model
|
||||||
|
|
||||||
class DataSource(var url: String = String())
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "sources")
|
||||||
|
data class DataSource(@PrimaryKey var sourceUrl: String = String())
|
||||||
@@ -24,12 +24,8 @@ import android.hardware.SensorManager
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import com.rtbishop.look4sat.domain.Constants
|
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
import com.rtbishop.look4sat.framework.PreferencesSource
|
||||||
import com.rtbishop.look4sat.framework.local.Converters
|
import com.rtbishop.look4sat.framework.local.*
|
||||||
import com.rtbishop.look4sat.framework.local.MIGRATION_1_2
|
|
||||||
import com.rtbishop.look4sat.framework.local.SatelliteDao
|
|
||||||
import com.rtbishop.look4sat.framework.local.SatelliteDb
|
|
||||||
import com.rtbishop.look4sat.framework.remote.SatelliteApi
|
import com.rtbishop.look4sat.framework.remote.SatelliteApi
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -74,18 +70,17 @@ object AppModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providePreferenceSource(
|
fun providePreferenceSource(
|
||||||
moshi: Moshi,
|
|
||||||
locationManager: LocationManager,
|
locationManager: LocationManager,
|
||||||
preferences: SharedPreferences
|
preferences: SharedPreferences
|
||||||
): PreferencesSource {
|
): PreferencesSource {
|
||||||
return PreferencesSource(moshi, locationManager, preferences)
|
return PreferencesSource(locationManager, preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSatelliteApi(): SatelliteApi {
|
fun provideSatelliteApi(): SatelliteApi {
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.baseUrl(Constants.URL_BASE)
|
.baseUrl("https://db.satnogs.org/api/")
|
||||||
.addConverterFactory(MoshiConverterFactory.create())
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
.build().create(SatelliteApi::class.java)
|
.build().create(SatelliteApi::class.java)
|
||||||
}
|
}
|
||||||
@@ -96,11 +91,17 @@ object AppModule {
|
|||||||
return db.satelliteDao()
|
return db.satelliteDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSourcesDao(db: SatelliteDb): SourcesDao {
|
||||||
|
return db.sourcesDao()
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatelliteDb {
|
fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatelliteDb {
|
||||||
Converters.initialize(moshi)
|
Converters.initialize(moshi)
|
||||||
return Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb")
|
return Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb")
|
||||||
.addMigrations(MIGRATION_1_2).build()
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,11 @@ import javax.inject.Singleton
|
|||||||
object CoreModule {
|
object CoreModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideLocalDataSource(satelliteDao: SatelliteDao): LocalDataSource {
|
fun provideLocalDataSource(
|
||||||
return LocalSource(satelliteDao)
|
satelliteDao: SatelliteDao,
|
||||||
|
sourcesDao: SourcesDao
|
||||||
|
): LocalDataSource {
|
||||||
|
return LocalSource(satelliteDao, sourcesDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
+1
-2
@@ -37,7 +37,7 @@ import javax.inject.Inject
|
|||||||
class EntriesViewModel @Inject constructor(
|
class EntriesViewModel @Inject constructor(
|
||||||
private val preferences: PreferencesSource,
|
private val preferences: PreferencesSource,
|
||||||
private val resolver: ContentResolver,
|
private val resolver: ContentResolver,
|
||||||
private val satelliteRepo: SatelliteRepo,
|
private val satelliteRepo: SatelliteRepo
|
||||||
) : ViewModel(), SearchView.OnQueryTextListener {
|
) : ViewModel(), SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
|
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
@@ -72,7 +72,6 @@ class EntriesViewModel @Inject constructor(
|
|||||||
fun updateEntriesFromWeb(sources: List<String>) {
|
fun updateEntriesFromWeb(sources: List<String>) {
|
||||||
viewModelScope.launch(coroutineHandler) {
|
viewModelScope.launch(coroutineHandler) {
|
||||||
_satData.value = DataState.Loading
|
_satData.value = DataState.Loading
|
||||||
preferences.saveTleSources(sources)
|
|
||||||
satelliteRepo.updateEntriesFromWeb(sources)
|
satelliteRepo.updateEntriesFromWeb(sources)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -41,9 +41,14 @@ import java.util.*
|
|||||||
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
|
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
|
||||||
|
|
||||||
private val passesViewModel: PassesViewModel by viewModels()
|
private val passesViewModel: PassesViewModel by viewModels()
|
||||||
private val permRequest = ActivityResultContracts.RequestPermission()
|
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
|
||||||
private val permRequestLauncher = registerForActivityResult(permRequest) {
|
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
passesViewModel.triggerInitialSetup()
|
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
|
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
|
||||||
|
when {
|
||||||
|
permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup()
|
||||||
|
permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -68,7 +73,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
|
|||||||
handleNewPasses(passesResult, passesAdapter, binding)
|
handleNewPasses(passesResult, passesAdapter, binding)
|
||||||
})
|
})
|
||||||
passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone ->
|
passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone ->
|
||||||
if (!setupDone) permRequestLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +100,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
|
|||||||
passesProgress.visibility = View.VISIBLE
|
passesProgress.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DataState.Error -> {
|
else -> {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
passesTimer.text = 0L.toTimerString()
|
passesTimer.text = 0L.toTimerString()
|
||||||
passesProgress.visibility = View.INVISIBLE
|
passesProgress.visibility = View.INVISIBLE
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ class PassesViewModel @Inject constructor(
|
|||||||
val stationPos = preferences.loadStationPosition()
|
val stationPos = preferences.loadStationPosition()
|
||||||
val hoursAhead = preferences.getHoursAhead()
|
val hoursAhead = preferences.getHoursAhead()
|
||||||
val minElev = preferences.getMinElevation()
|
val minElev = preferences.getMinElevation()
|
||||||
satelliteRepo.updateEntriesFromWeb(preferences.loadDefaultSources())
|
satelliteRepo.updateEntriesFromWeb(satelliteRepo.getDefaultSources())
|
||||||
satelliteRepo.updateEntriesSelection(defaultCatNums, true)
|
satelliteRepo.updateEntriesSelection(defaultCatNums, true)
|
||||||
predictor.forceCalculation(satellites, stationPos, dateNow, hoursAhead, minElev)
|
predictor.forceCalculation(satellites, stationPos, dateNow, hoursAhead, minElev)
|
||||||
preferences.setSetupDone()
|
preferences.setSetupDone()
|
||||||
|
|||||||
+10
-8
@@ -39,14 +39,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var preferences: PreferencesSource
|
lateinit var preferences: PreferencesSource
|
||||||
|
|
||||||
private val requestPermissionLauncher =
|
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
if (isGranted) {
|
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
updatePositionFromGPS()
|
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
|
||||||
} else {
|
when {
|
||||||
showSnack(getString(R.string.pref_pos_gps_error))
|
permissions[locPermFine] == true -> updatePositionFromGPS()
|
||||||
}
|
permissions[locPermCoarse] == true -> updatePositionFromGPS()
|
||||||
|
else -> showSnack(getString(R.string.pref_pos_gps_error))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.preference, rootKey)
|
setPreferencesFromResource(R.xml.preference, rootKey)
|
||||||
@@ -113,7 +115,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||||||
} else {
|
} else {
|
||||||
showSnack(getString(R.string.pref_pos_gps_null))
|
showSnack(getString(R.string.pref_pos_gps_null))
|
||||||
}
|
}
|
||||||
} else requestPermissionLauncher.launch(locPermString)
|
} else locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSnack(message: String) {
|
private fun showSnack(message: String) {
|
||||||
|
|||||||
+4
-3
@@ -22,12 +22,13 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.rtbishop.look4sat.databinding.ItemSourceBinding
|
import com.rtbishop.look4sat.databinding.ItemSourceBinding
|
||||||
|
import com.rtbishop.look4sat.framework.model.DataSource
|
||||||
|
|
||||||
class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListOf()) :
|
class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListOf()) :
|
||||||
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
|
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
|
||||||
|
|
||||||
fun getSources(): List<DataSource> {
|
fun getSources(): List<DataSource> {
|
||||||
return sources.filter { it.url.contains("https://") }
|
return sources.filter { it.sourceUrl.contains("https://") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSources(list: List<DataSource>) {
|
fun setSources(list: List<DataSource>) {
|
||||||
@@ -58,8 +59,8 @@ class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListO
|
|||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(source: DataSource) {
|
fun bind(source: DataSource) {
|
||||||
binding.sourceUrl.setText(source.url)
|
binding.sourceUrl.setText(source.sourceUrl)
|
||||||
binding.sourceUrl.doOnTextChanged { text, _, _, _ -> source.url = text.toString() }
|
binding.sourceUrl.doOnTextChanged { text, _, _, _ -> source.sourceUrl = text.toString() }
|
||||||
binding.sourceInput.setEndIconOnClickListener {
|
binding.sourceInput.setEndIconOnClickListener {
|
||||||
sources.remove(source)
|
sources.remove(source)
|
||||||
notifyItemRemoved(adapterPosition)
|
notifyItemRemoved(adapterPosition)
|
||||||
|
|||||||
+23
-23
@@ -23,19 +23,18 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatDialogFragment
|
import androidx.appcompat.app.AppCompatDialogFragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.rtbishop.look4sat.R
|
import com.rtbishop.look4sat.R
|
||||||
import com.rtbishop.look4sat.databinding.DialogSourcesBinding
|
import com.rtbishop.look4sat.databinding.DialogSourcesBinding
|
||||||
import com.rtbishop.look4sat.framework.PreferencesSource
|
import com.rtbishop.look4sat.framework.model.DataSource
|
||||||
import com.rtbishop.look4sat.utility.setNavResult
|
import com.rtbishop.look4sat.utility.setNavResult
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SourcesDialog : AppCompatDialogFragment() {
|
class SourcesDialog : AppCompatDialogFragment() {
|
||||||
|
|
||||||
@Inject
|
private val viewModel: SourcesViewModel by viewModels()
|
||||||
lateinit var prefsManager: PreferencesSource
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.dialog_sources, group, false)
|
return inflater.inflate(R.layout.dialog_sources, group, false)
|
||||||
@@ -43,25 +42,26 @@ class SourcesDialog : AppCompatDialogFragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, state: Bundle?) {
|
override fun onViewCreated(view: View, state: Bundle?) {
|
||||||
super.onViewCreated(view, state)
|
super.onViewCreated(view, state)
|
||||||
val sources = prefsManager.loadTleSources().map { DataSource(it) }
|
viewModel.sources.observe(viewLifecycleOwner, { sources ->
|
||||||
val sourcesAdapter = SourcesAdapter().apply { setSources(sources) }
|
val adapter = SourcesAdapter().apply { setSources(sources.map { DataSource(it) }) }
|
||||||
DialogSourcesBinding.bind(view).apply {
|
DialogSourcesBinding.bind(view).apply {
|
||||||
dialog?.window?.setLayout(
|
dialog?.window?.setLayout(
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
WindowManager.LayoutParams.MATCH_PARENT,
|
||||||
WindowManager.LayoutParams.WRAP_CONTENT
|
WindowManager.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
sourcesRecycler.apply {
|
sourcesRecycler.apply {
|
||||||
adapter = sourcesAdapter
|
this.adapter = adapter
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
}
|
||||||
|
sourcesBtnAdd.setOnClickListener {
|
||||||
|
adapter.addSource()
|
||||||
|
}
|
||||||
|
sourcesBtnPos.setOnClickListener {
|
||||||
|
setNavResult("sources", adapter.getSources().map { it.sourceUrl })
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
sourcesBtnNeg.setOnClickListener { dismiss() }
|
||||||
}
|
}
|
||||||
sourcesBtnAdd.setOnClickListener {
|
})
|
||||||
sourcesAdapter.addSource()
|
|
||||||
}
|
|
||||||
sourcesBtnPos.setOnClickListener {
|
|
||||||
setNavResult("sources", sourcesAdapter.getSources().map { it.url })
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
sourcesBtnNeg.setOnClickListener { dismiss() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.rtbishop.look4sat.presentation.sourcesScreen
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.liveData
|
||||||
|
import com.rtbishop.look4sat.domain.SatelliteRepo
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SourcesViewModel @Inject constructor(satelliteRepo: SatelliteRepo) : ViewModel() {
|
||||||
|
|
||||||
|
val sources = liveData {
|
||||||
|
emit(satelliteRepo.getSavedSources())
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
gradle_version = '7.0.2'
|
gradle_version = '7.0.3'
|
||||||
kotlin_version = '1.5.31'
|
kotlin_version = '1.5.31'
|
||||||
coroutines_version = '1.5.2-native-mt'
|
coroutines_version = '1.5.2-native-mt'
|
||||||
material_version = '1.4.0'
|
material_version = '1.4.0'
|
||||||
@@ -15,7 +15,7 @@ buildscript {
|
|||||||
osmdroid_version = '6.1.11'
|
osmdroid_version = '6.1.11'
|
||||||
timber_version = '5.0.1'
|
timber_version = '5.0.1'
|
||||||
junit_version = '4.13.2'
|
junit_version = '4.13.2'
|
||||||
mockito_version = '3.12.4'
|
mockito_version = '4.0.0'
|
||||||
leak_canary_version = '2.7'
|
leak_canary_version = '2.7'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -45,6 +45,22 @@ class DataRepository(
|
|||||||
return localSource.getTransmitters(catNum)
|
return localSource.getTransmitters(catNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDefaultSources(): List<String> {
|
||||||
|
return listOf(
|
||||||
|
"https://celestrak.com/NORAD/elements/active.txt",
|
||||||
|
"https://amsat.org/tle/current/nasabare.txt",
|
||||||
|
"https://www.prismnet.com/~mmccants/tles/classfd.zip",
|
||||||
|
"https://www.prismnet.com/~mmccants/tles/inttles.zip"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSavedSources(): List<String> {
|
||||||
|
val savedSources = localSource.getSources()
|
||||||
|
return if (savedSources.isEmpty()) {
|
||||||
|
getDefaultSources()
|
||||||
|
} else savedSources
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getSelectedSatellites(): List<Satellite> {
|
override suspend fun getSelectedSatellites(): List<Satellite> {
|
||||||
return localSource.getSelectedSatellites()
|
return localSource.getSelectedSatellites()
|
||||||
}
|
}
|
||||||
@@ -55,6 +71,9 @@ class DataRepository(
|
|||||||
|
|
||||||
override suspend fun updateEntriesFromWeb(sources: List<String>) {
|
override suspend fun updateEntriesFromWeb(sources: List<String>) {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
|
launch(repoDispatcher) {
|
||||||
|
localSource.updateSources(sources)
|
||||||
|
}
|
||||||
launch(repoDispatcher) {
|
launch(repoDispatcher) {
|
||||||
val streams = mutableListOf<InputStream>()
|
val streams = mutableListOf<InputStream>()
|
||||||
val entries = mutableListOf<SatEntry>()
|
val entries = mutableListOf<SatEntry>()
|
||||||
|
|||||||
@@ -29,11 +29,15 @@ interface LocalDataSource {
|
|||||||
|
|
||||||
fun getTransmitters(catNum: Int): Flow<List<Transmitter>>
|
fun getTransmitters(catNum: Int): Flow<List<Transmitter>>
|
||||||
|
|
||||||
|
suspend fun getSources(): List<String>
|
||||||
|
|
||||||
suspend fun getSelectedSatellites(): List<Satellite>
|
suspend fun getSelectedSatellites(): List<Satellite>
|
||||||
|
|
||||||
suspend fun updateEntries(entries: List<SatEntry>)
|
suspend fun updateEntries(entries: List<SatEntry>)
|
||||||
|
|
||||||
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean)
|
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean)
|
||||||
|
|
||||||
|
suspend fun updateSources(sources: List<String>)
|
||||||
|
|
||||||
suspend fun updateTransmitters(transmitters: List<Transmitter>)
|
suspend fun updateTransmitters(transmitters: List<Transmitter>)
|
||||||
}
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
|
||||||
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.rtbishop.look4sat.domain
|
|
||||||
|
|
||||||
object Constants {
|
|
||||||
|
|
||||||
const val URL_BASE = "https://db.satnogs.org/api/"
|
|
||||||
const val URL_CELESTRAK = "https://celestrak.com/NORAD/elements/active.txt"
|
|
||||||
const val URL_AMSAT = "https://amsat.org/tle/current/nasabare.txt"
|
|
||||||
const val URL_PRISM_CLASSFD = "https://www.prismnet.com/~mmccants/tles/classfd.zip"
|
|
||||||
const val URL_PRISM_INTEL = "https://www.prismnet.com/~mmccants/tles/inttles.zip"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.rtbishop.look4sat.domain
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.domain.predict.GeoPos
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
||||||
|
interface LocationProvider {
|
||||||
|
|
||||||
|
val updatedLocation: SharedFlow<GeoPos?>
|
||||||
|
}
|
||||||
@@ -52,15 +52,15 @@ object QthConverter {
|
|||||||
return "$lonFirst$latFirst$lonSecond$latSecond$lonThird$latThird"
|
return "$lonFirst$latFirst$lonSecond$latSecond$lonThird$latThird"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isValidPosition(lat: Double, lon: Double): Boolean {
|
||||||
|
return (lat > -90.0 && lat < 90.0) && (lon > -180.0 && lon < 360.0)
|
||||||
|
}
|
||||||
|
|
||||||
private fun isValidQth(qthString: String): Boolean {
|
private fun isValidQth(qthString: String): Boolean {
|
||||||
val qthPattern = "[a-xA-X][a-xA-X][0-9][0-9][a-xA-X][a-xA-X]".toRegex()
|
val qthPattern = "[a-xA-X][a-xA-X][0-9][0-9][a-xA-X][a-xA-X]".toRegex()
|
||||||
return qthString.matches(qthPattern)
|
return qthString.matches(qthPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isValidPosition(lat: Double, lon: Double): Boolean {
|
|
||||||
return (lat > -90.0 && lat < 90.0) && (lon > -180.0 && lon < 360.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Double.roundToDecimals(decimals: Int): Double {
|
private fun Double.roundToDecimals(decimals: Int): Double {
|
||||||
var multiplier = 1.0
|
var multiplier = 1.0
|
||||||
repeat(decimals) { multiplier *= 10 }
|
repeat(decimals) { multiplier *= 10 }
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ interface SatelliteRepo {
|
|||||||
|
|
||||||
fun getSatTransmitters(catNum: Int): Flow<List<Transmitter>>
|
fun getSatTransmitters(catNum: Int): Flow<List<Transmitter>>
|
||||||
|
|
||||||
|
fun getDefaultSources(): List<String>
|
||||||
|
|
||||||
|
suspend fun getSavedSources(): List<String>
|
||||||
|
|
||||||
suspend fun getSelectedSatellites(): List<Satellite>
|
suspend fun getSelectedSatellites(): List<Satellite>
|
||||||
|
|
||||||
suspend fun updateEntriesFromFile(stream: InputStream)
|
suspend fun updateEntriesFromFile(stream: InputStream)
|
||||||
|
|||||||
@@ -17,4 +17,4 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.domain.predict
|
package com.rtbishop.look4sat.domain.predict
|
||||||
|
|
||||||
data class GeoPos(val latitude: Double, val longitude: Double, val altitude: Double = 0.0)
|
data class GeoPos(val latitude: Double, val longitude: Double, val name: String? = null)
|
||||||
@@ -168,10 +168,10 @@ abstract class Satellite(val params: TLE) {
|
|||||||
val c =
|
val c =
|
||||||
invert(sqrt(1.0 + flatFactor * (flatFactor - 2) * sqr(sin(deg2Rad * gsPos.latitude))))
|
invert(sqrt(1.0 + flatFactor * (flatFactor - 2) * sqr(sin(deg2Rad * gsPos.latitude))))
|
||||||
val sq = sqr(1.0 - flatFactor) * c
|
val sq = sqr(1.0 - flatFactor) * c
|
||||||
val achcp = (earthRadius * c + gsPos.altitude / 1000.0) * cos(deg2Rad * gsPos.latitude)
|
val achcp = (earthRadius * c) * cos(deg2Rad * gsPos.latitude)
|
||||||
obsPos.setXYZ(
|
obsPos.setXYZ(
|
||||||
achcp * cos(gsPosTheta.get()), achcp * sin(gsPosTheta.get()),
|
achcp * cos(gsPosTheta.get()), achcp * sin(gsPosTheta.get()),
|
||||||
(earthRadius * sq + gsPos.altitude / 1000.0) * sin(deg2Rad * gsPos.latitude)
|
(earthRadius * sq) * sin(deg2Rad * gsPos.latitude)
|
||||||
)
|
)
|
||||||
obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0)
|
obsVel.setXYZ(-mFactor * obsPos.y, mFactor * obsPos.x, 0.0)
|
||||||
magnitude(obsPos)
|
magnitude(obsPos)
|
||||||
|
|||||||
Reference in new issue
Block a user