Moved sources urls to database storage

pull/87/head
Arty Bishop 2021-10-10 20:49:41 +01:00
rodzic af7e027350
commit 5f4c63ee49
27 zmienionych plików z 418 dodań i 137 usunięć

Wyświetl plik

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "825f728737780384e51a6a73221288c4",
"identityHash": "555494c464b5a29285eb5493cc8aa5f6",
"entities": [
{
"tableName": "entries",
@ -103,12 +103,32 @@
},
"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, '825f728737780384e51a6a73221288c4')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '555494c464b5a29285eb5493cc8aa5f6')"
]
}
}

Wyświetl plik

@ -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')"
]
}
}

Wyświetl plik

@ -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))
}
}

Wyświetl plik

@ -22,8 +22,10 @@ import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@Singleton
class OrientationSource @Inject constructor(private val sensorManager: SensorManager) :
SensorEventListener {

Wyświetl plik

@ -21,50 +21,19 @@ import android.content.SharedPreferences
import android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.core.content.edit
import com.rtbishop.look4sat.domain.Constants
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.utility.round
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PreferencesSource @Inject constructor(
moshi: Moshi,
private val locationManager: LocationManager,
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 {
const val keySources = "prefTleSourcesKey"
const val keyModes = "satModes"
const val keyCompass = "compass"
const val keyRadarSweep = "radarSweep"
@ -78,7 +47,6 @@ class PreferencesSource @Inject constructor(
const val keyRotatorPort = "rotatorPort"
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyAltitude = "stationAlt"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
@ -87,15 +55,13 @@ class PreferencesSource @Inject constructor(
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 GeoPos(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
return GeoPos(latitude.toDouble(), longitude.toDouble())
}
fun saveStationPosition(pos: GeoPos) {
preferences.edit {
putString(keyLatitude, pos.latitude.toString())
putString(keyLongitude, pos.longitude.toString())
putString(keyAltitude, pos.altitude.toString())
}
}
@ -106,8 +72,7 @@ class PreferencesSource @Inject constructor(
else {
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1)
val stationPosition = GeoPos(latitude, longitude, altitude)
val stationPosition = GeoPos(latitude, longitude)
saveStationPosition(stationPosition)
return true
}
@ -118,7 +83,7 @@ class PreferencesSource @Inject constructor(
fun updatePositionFromQTH(qthString: String): Boolean {
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)
return true
}
@ -127,8 +92,7 @@ class PreferencesSource @Inject constructor(
val stationPosition = loadStationPosition()
val lat = stationPosition.latitude.toFloat()
val lon = stationPosition.longitude.toFloat()
val alt = stationPosition.altitude.toFloat()
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
return GeomagneticField(lat, lon, 0f, System.currentTimeMillis()).declination
}
fun getHoursAhead(): Int {

Wyświetl plik

@ -23,16 +23,24 @@ import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.framework.DataMapper
import com.rtbishop.look4sat.framework.model.DataSource
import kotlinx.coroutines.flow.Flow
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>> {
return satelliteDao.getSatItems()
.map { satItems -> DataMapper.satItemsToDomainItems(satItems) }
}
override suspend fun getSources(): List<String> {
return sourcesDao.getSources()
}
override suspend fun getSelectedSatellites(): List<Satellite> {
return satelliteDao.getSelectedSatellites()
}
@ -46,6 +54,11 @@ class LocalSource(private val satelliteDao: SatelliteDao) : LocalDataSource {
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>> {
return satelliteDao.getSatTransmitters(catNum)
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }

Wyświetl plik

@ -22,14 +22,21 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
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.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)
abstract class SatelliteDb : RoomDatabase() {
abstract fun satelliteDao(): SatelliteDao
abstract fun sourcesDao(): SourcesDao
}
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")
}
}
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))")
}
}

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -15,6 +15,10 @@
* 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.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())

Wyświetl plik

@ -24,12 +24,8 @@ import android.hardware.SensorManager
import android.location.LocationManager
import androidx.preference.PreferenceManager
import androidx.room.Room
import com.rtbishop.look4sat.domain.Constants
import com.rtbishop.look4sat.framework.PreferencesSource
import com.rtbishop.look4sat.framework.local.Converters
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.local.*
import com.rtbishop.look4sat.framework.remote.SatelliteApi
import com.squareup.moshi.Moshi
import dagger.Module
@ -74,18 +70,17 @@ object AppModule {
@Provides
@Singleton
fun providePreferenceSource(
moshi: Moshi,
locationManager: LocationManager,
preferences: SharedPreferences
): PreferencesSource {
return PreferencesSource(moshi, locationManager, preferences)
return PreferencesSource(locationManager, preferences)
}
@Provides
@Singleton
fun provideSatelliteApi(): SatelliteApi {
return Retrofit.Builder()
.baseUrl(Constants.URL_BASE)
.baseUrl("https://db.satnogs.org/api/")
.addConverterFactory(MoshiConverterFactory.create())
.build().create(SatelliteApi::class.java)
}
@ -96,11 +91,17 @@ object AppModule {
return db.satelliteDao()
}
@Provides
@Singleton
fun provideSourcesDao(db: SatelliteDb): SourcesDao {
return db.sourcesDao()
}
@Provides
@Singleton
fun provideSatelliteDb(@ApplicationContext context: Context, moshi: Moshi): SatelliteDb {
Converters.initialize(moshi)
return Room.databaseBuilder(context, SatelliteDb::class.java, "SatelliteDb")
.addMigrations(MIGRATION_1_2).build()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
}
}

Wyświetl plik

@ -38,8 +38,11 @@ import javax.inject.Singleton
object CoreModule {
@Provides
fun provideLocalDataSource(satelliteDao: SatelliteDao): LocalDataSource {
return LocalSource(satelliteDao)
fun provideLocalDataSource(
satelliteDao: SatelliteDao,
sourcesDao: SourcesDao
): LocalDataSource {
return LocalSource(satelliteDao, sourcesDao)
}
@Provides

Wyświetl plik

@ -37,7 +37,7 @@ import javax.inject.Inject
class EntriesViewModel @Inject constructor(
private val preferences: PreferencesSource,
private val resolver: ContentResolver,
private val satelliteRepo: SatelliteRepo,
private val satelliteRepo: SatelliteRepo
) : ViewModel(), SearchView.OnQueryTextListener {
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
@ -72,7 +72,6 @@ class EntriesViewModel @Inject constructor(
fun updateEntriesFromWeb(sources: List<String>) {
viewModelScope.launch(coroutineHandler) {
_satData.value = DataState.Loading
preferences.saveTleSources(sources)
satelliteRepo.updateEntriesFromWeb(sources)
}
}

Wyświetl plik

@ -41,9 +41,14 @@ import java.util.*
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
private val passesViewModel: PassesViewModel by viewModels()
private val permRequest = ActivityResultContracts.RequestPermission()
private val permRequestLauncher = registerForActivityResult(permRequest) {
passesViewModel.triggerInitialSetup()
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
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?) {
@ -68,7 +73,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
handleNewPasses(passesResult, passesAdapter, binding)
})
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
}
}
is DataState.Error -> {
else -> {
binding.apply {
passesTimer.text = 0L.toTimerString()
passesProgress.visibility = View.INVISIBLE

Wyświetl plik

@ -77,7 +77,7 @@ class PassesViewModel @Inject constructor(
val stationPos = preferences.loadStationPosition()
val hoursAhead = preferences.getHoursAhead()
val minElev = preferences.getMinElevation()
satelliteRepo.updateEntriesFromWeb(preferences.loadDefaultSources())
satelliteRepo.updateEntriesFromWeb(satelliteRepo.getDefaultSources())
satelliteRepo.updateEntriesSelection(defaultCatNums, true)
predictor.forceCalculation(satellites, stationPos, dateNow, hoursAhead, minElev)
preferences.setSetupDone()

Wyświetl plik

@ -39,14 +39,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var preferences: PreferencesSource
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
updatePositionFromGPS()
} else {
showSnack(getString(R.string.pref_pos_gps_error))
}
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
when {
permissions[locPermFine] == true -> updatePositionFromGPS()
permissions[locPermCoarse] == true -> updatePositionFromGPS()
else -> showSnack(getString(R.string.pref_pos_gps_error))
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preference, rootKey)
@ -113,7 +115,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
} else {
showSnack(getString(R.string.pref_pos_gps_null))
}
} else requestPermissionLauncher.launch(locPermString)
} else locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
}
private fun showSnack(message: String) {

Wyświetl plik

@ -22,12 +22,13 @@ import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.databinding.ItemSourceBinding
import com.rtbishop.look4sat.framework.model.DataSource
class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListOf()) :
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
fun getSources(): List<DataSource> {
return sources.filter { it.url.contains("https://") }
return sources.filter { it.sourceUrl.contains("https://") }
}
fun setSources(list: List<DataSource>) {
@ -58,8 +59,8 @@ class SourcesAdapter(private val sources: MutableList<DataSource> = mutableListO
RecyclerView.ViewHolder(binding.root) {
fun bind(source: DataSource) {
binding.sourceUrl.setText(source.url)
binding.sourceUrl.doOnTextChanged { text, _, _, _ -> source.url = text.toString() }
binding.sourceUrl.setText(source.sourceUrl)
binding.sourceUrl.doOnTextChanged { text, _, _, _ -> source.sourceUrl = text.toString() }
binding.sourceInput.setEndIconOnClickListener {
sources.remove(source)
notifyItemRemoved(adapterPosition)

Wyświetl plik

@ -23,19 +23,18 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R
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 dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class SourcesDialog : AppCompatDialogFragment() {
@Inject
lateinit var prefsManager: PreferencesSource
private val viewModel: SourcesViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_sources, group, false)
@ -43,25 +42,26 @@ class SourcesDialog : AppCompatDialogFragment() {
override fun onViewCreated(view: View, state: Bundle?) {
super.onViewCreated(view, state)
val sources = prefsManager.loadTleSources().map { DataSource(it) }
val sourcesAdapter = SourcesAdapter().apply { setSources(sources) }
DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
sourcesRecycler.apply {
adapter = sourcesAdapter
layoutManager = LinearLayoutManager(requireContext())
viewModel.sources.observe(viewLifecycleOwner, { sources ->
val adapter = SourcesAdapter().apply { setSources(sources.map { DataSource(it) }) }
DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
sourcesRecycler.apply {
this.adapter = adapter
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() }
}
})
}
}

Wyświetl plik

@ -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())
}
}

Wyświetl plik

@ -1,6 +1,6 @@
buildscript {
ext {
gradle_version = '7.0.2'
gradle_version = '7.0.3'
kotlin_version = '1.5.31'
coroutines_version = '1.5.2-native-mt'
material_version = '1.4.0'
@ -15,7 +15,7 @@ buildscript {
osmdroid_version = '6.1.11'
timber_version = '5.0.1'
junit_version = '4.13.2'
mockito_version = '3.12.4'
mockito_version = '4.0.0'
leak_canary_version = '2.7'
}
repositories {

Wyświetl plik

@ -45,6 +45,22 @@ class DataRepository(
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> {
return localSource.getSelectedSatellites()
}
@ -55,6 +71,9 @@ class DataRepository(
override suspend fun updateEntriesFromWeb(sources: List<String>) {
coroutineScope {
launch(repoDispatcher) {
localSource.updateSources(sources)
}
launch(repoDispatcher) {
val streams = mutableListOf<InputStream>()
val entries = mutableListOf<SatEntry>()

Wyświetl plik

@ -29,11 +29,15 @@ interface LocalDataSource {
fun getTransmitters(catNum: Int): Flow<List<Transmitter>>
suspend fun getSources(): List<String>
suspend fun getSelectedSatellites(): List<Satellite>
suspend fun updateEntries(entries: List<SatEntry>)
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean)
suspend fun updateSources(sources: List<String>)
suspend fun updateTransmitters(transmitters: List<Transmitter>)
}

Wyświetl plik

@ -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"
}

Wyświetl plik

@ -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?>
}

Wyświetl plik

@ -52,15 +52,15 @@ object QthConverter {
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 {
val qthPattern = "[a-xA-X][a-xA-X][0-9][0-9][a-xA-X][a-xA-X]".toRegex()
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 {
var multiplier = 1.0
repeat(decimals) { multiplier *= 10 }

Wyświetl plik

@ -29,6 +29,10 @@ interface SatelliteRepo {
fun getSatTransmitters(catNum: Int): Flow<List<Transmitter>>
fun getDefaultSources(): List<String>
suspend fun getSavedSources(): List<String>
suspend fun getSelectedSatellites(): List<Satellite>
suspend fun updateEntriesFromFile(stream: InputStream)

Wyświetl plik

@ -17,4 +17,4 @@
*/
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)

Wyświetl plik

@ -168,10 +168,10 @@ abstract class Satellite(val params: TLE) {
val c =
invert(sqrt(1.0 + flatFactor * (flatFactor - 2) * sqr(sin(deg2Rad * gsPos.latitude))))
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(
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)
magnitude(obsPos)