Moved from a file-based TLE storage to a Room-based one. Fixed entries selection issue.

This commit is contained in:
Arty Bishop committed 2020-04-23 19:40:42 +01:00
1 parent 0188248bc1
commit cf86892ad5
47 files changed
+806 -684

No files matched your search

@@ -20,8 +20,8 @@
package com.rtbishop.look4sat
import android.app.Application
import com.rtbishop.look4sat.di.AppComponent
import com.rtbishop.look4sat.di.DaggerAppComponent
import com.rtbishop.look4sat.dagger.AppComponent
import com.rtbishop.look4sat.dagger.DaggerAppComponent
class Look4SatApp : Application() {
@@ -1,176 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.SatNotFoundException
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.predict4kotlin.PassPredictor
import com.rtbishop.look4sat.repo.Repository
import com.rtbishop.look4sat.repo.SatPass
import com.rtbishop.look4sat.repo.Transmitter
import com.rtbishop.look4sat.utility.DataManager
import com.rtbishop.look4sat.utility.PrefsManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.util.*
import javax.inject.Inject
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val app = application
private val urlList = listOf("https://celestrak.com/NORAD/elements/active.txt")
private val _satPassList = MutableLiveData<MutableList<SatPass>>()
private val _debugMessage = MutableLiveData<String>()
private val _gsp = MutableLiveData<GroundStationPosition>()
private val _isListRefreshing = MutableLiveData<Boolean>()
private var calculationJob: Job? = null
@Inject
lateinit var repository: Repository
@Inject
lateinit var dataManager: DataManager
@Inject
lateinit var prefsManager: PrefsManager
init {
(app as Look4SatApp).appComponent.inject(this)
setGroundStationPosition()
}
var isFirstLaunch = true
var tleMainList = dataManager.loadTleList()
var tleSelection = dataManager.loadSelectionList()
fun getGSP(): LiveData<GroundStationPosition> = _gsp
fun getDebugMessage(): LiveData<String> = _debugMessage
fun getSatPassList(): LiveData<MutableList<SatPass>> = _satPassList
fun getRefreshing(): LiveData<Boolean> = _isListRefreshing
fun getRefreshRate() = prefsManager.getRefreshRate()
fun getHoursAhead() = prefsManager.getHoursAhead()
fun getMinElevation() = prefsManager.getMinElevation()
fun setGroundStationPosition() = _gsp.postValue(prefsManager.getGroundStationPosition())
fun updateLocation() {
dataManager.getLastKnownLocation()?.let {
prefsManager.setGroundStationPosition(it)
_gsp.postValue(it)
}
}
fun setPassPrefs(hoursAhead: Int, minEl: Double) {
prefsManager.setHoursAhead(hoursAhead)
prefsManager.setMinElevation(minEl)
}
fun updateAndSaveTleFile() {
viewModelScope.launch(Dispatchers.IO) {
try {
val inputStream = repository.getStreamForUrl(urlList)
val tleList = TLE.importSat(inputStream).apply { sortBy { it.name } }
dataManager.saveTleList(tleList)
tleMainList = tleList
updateAndSaveSelectionList(mutableListOf())
_debugMessage.postValue(app.getString(R.string.update_tle_success))
} catch (exception: IOException) {
_debugMessage.postValue(app.getString(R.string.update_failure))
}
}
}
fun updateTransmittersDatabase() {
viewModelScope.launch(Dispatchers.IO) {
try {
repository.updateTransmittersDatabase()
_debugMessage.postValue(app.getString(R.string.update_trans_success))
} catch (exception: IOException) {
_debugMessage.postValue(app.getString(R.string.update_failure))
}
}
}
fun updateAndSaveSelectionList(list: MutableList<Int>) {
tleSelection = list
dataManager.saveSelectionList(list)
}
fun getPasses() {
_isListRefreshing.value = true
calculationJob?.cancel()
var passList = mutableListOf<SatPass>()
calculationJob = viewModelScope.launch {
if (tleMainList.isNotEmpty() && tleSelection.isNotEmpty()) {
withContext(Dispatchers.Default) {
val dateNow = Date()
tleSelection.forEach { indexOfSelection ->
val tle = tleMainList[indexOfSelection]
try {
val gsp = getGSP().value ?: GroundStationPosition(0.0, 0.0, 0.0)
val predictor = PassPredictor(tle, gsp)
val passes = predictor.getPasses(dateNow, getHoursAhead(), true)
passes.forEach {
passList.add(SatPass(tle, predictor, it))
}
} catch (exception: IllegalArgumentException) {
_debugMessage.postValue(app.getString(R.string.err_parse_tle))
} catch (exception: SatNotFoundException) {
_debugMessage.postValue(app.getString(R.string.err_sat_wont_pass))
}
}
passList = filterAndSortList(passList, getHoursAhead())
}
_satPassList.postValue(passList)
} else {
_satPassList.postValue(mutableListOf())
_debugMessage.postValue(app.getString(R.string.err_no_sat_selected))
}
_isListRefreshing.value = false
}
}
private fun filterAndSortList(
list: MutableList<SatPass>,
hoursAhead: Int
): MutableList<SatPass> {
val dateNow = Date()
val dateFuture = Calendar.getInstance().let {
it.time = dateNow
it.add(Calendar.HOUR, hoursAhead)
it.time
}
list.removeAll { it.pass.startTime.after(dateFuture) }
list.removeAll { it.pass.endTime.before(dateNow) }
list.removeAll { it.pass.maxEl < getMinElevation() }
list.sortBy { it.pass.startTime }
return list
}
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
return repository.getTransmittersForSat(id)
}
}
@@ -17,16 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.di
package com.rtbishop.look4sat.dagger
import android.content.Context
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.dagger.modules.*
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.fragments.MapViewFragment
import com.rtbishop.look4sat.ui.fragments.PassListFragment
import com.rtbishop.look4sat.ui.fragments.PolarViewFragment
import dagger.BindsInstance
import dagger.Component
import javax.inject.Singleton
@Singleton
@Component(modules = [AndroidModule::class, NetworkModule::class, StorageModule::class, UtilityModule::class])
@Component(
modules = [NetworkModule::class, PersistenceModule::class,
RepoModule::class, UtilityModule::class, ViewModelModule::class]
)
interface AppComponent {
@Component.Factory
@@ -34,5 +41,8 @@ interface AppComponent {
fun create(@BindsInstance context: Context): AppComponent
}
fun inject(viewModel: MainViewModel)
fun inject(mainActivity: MainActivity)
fun inject(mapViewFragment: MapViewFragment)
fun inject(passListFragment: PassListFragment)
fun inject(polarViewFragment: PolarViewFragment)
}
@@ -0,0 +1,25 @@
package com.rtbishop.look4sat.dagger
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Singleton
class ViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("Unsupported ViewModel class: $modelClass")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
@@ -0,0 +1,14 @@
package com.rtbishop.look4sat.dagger
import androidx.lifecycle.ViewModel
import dagger.MapKey
import kotlin.reflect.KClass
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.di
package com.rtbishop.look4sat.dagger.modules
import com.rtbishop.look4sat.network.RemoteDataSource
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.network.TransmittersApi
import dagger.Module
import dagger.Provides
@@ -30,6 +32,18 @@ import javax.inject.Singleton
@Module
class NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
@Singleton
@Provides
fun provideRemoteDataSource(api: TransmittersApi, client: OkHttpClient): RemoteSource {
return RemoteDataSource(api, client)
}
@Singleton
@Provides
fun provideTransmittersApi(): TransmittersApi {
@@ -39,10 +53,4 @@ class NetworkModule {
.build()
.create(TransmittersApi::class.java)
}
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
}
@@ -17,29 +17,47 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.di
package com.rtbishop.look4sat.dagger.modules
import android.content.Context
import androidx.room.Room
import com.rtbishop.look4sat.storage.TransmittersDao
import com.rtbishop.look4sat.storage.TransmittersDb
import com.rtbishop.look4sat.persistence.LocalDataSource
import com.rtbishop.look4sat.persistence.LocalSource
import com.rtbishop.look4sat.persistence.SatelliteDb
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class StorageModule {
class PersistenceModule {
@Singleton
@Provides
fun provideTransmittersDb(context: Context): TransmittersDb {
return Room.databaseBuilder(context, TransmittersDb::class.java, "transmitters")
.build()
fun provideEntriesDao(db: SatelliteDb): EntriesDao {
return db.entriesDao()
}
@Singleton
@Provides
fun provideTransmittersDao(db: TransmittersDb): TransmittersDao {
fun provideTransmittersDao(db: SatelliteDb): TransmittersDao {
return db.transmittersDao()
}
@Singleton
@Provides
fun provideLocalDataSource(
entriesDao: EntriesDao,
transmittersDao: TransmittersDao
): LocalSource {
return LocalDataSource(entriesDao, transmittersDao)
}
@Singleton
@Provides
fun provideSatelliteDb(context: Context): SatelliteDb {
return Room.databaseBuilder(context, SatelliteDb::class.java, "satellites")
.build()
}
}
@@ -0,0 +1,19 @@
package com.rtbishop.look4sat.dagger.modules
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.persistence.LocalSource
import com.rtbishop.look4sat.repo.DefaultRepository
import com.rtbishop.look4sat.repo.Repository
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class RepoModule {
@Singleton
@Provides
fun provideDefaultRepository(localSource: LocalSource, remoteSource: RemoteSource): Repository {
return DefaultRepository(localSource, remoteSource)
}
}
@@ -17,18 +17,19 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.di
package com.rtbishop.look4sat.dagger.modules
import android.content.Context
import android.content.SharedPreferences
import android.location.LocationManager
import androidx.preference.PreferenceManager
import com.rtbishop.look4sat.utility.PrefsManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class AndroidModule {
class UtilityModule {
@Singleton
@Provides
@@ -41,4 +42,13 @@ class AndroidModule {
fun provideLocationManager(context: Context): LocationManager {
return context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
@Singleton
@Provides
fun providePrefsManager(
sharedPreferences: SharedPreferences,
locationManager: LocationManager
): PrefsManager {
return PrefsManager(sharedPreferences, locationManager)
}
}
@@ -0,0 +1,22 @@
package com.rtbishop.look4sat.dagger.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.dagger.ViewModelKey
import com.rtbishop.look4sat.ui.SharedViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(SharedViewModel::class)
abstract fun bindSharedViewModel(viewModel: SharedViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
@@ -0,0 +1,12 @@
package com.rtbishop.look4sat.data
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.github.amsacode.predict4java.TLE
@Entity(tableName = "entries")
data class SatEntry(val tle: TLE, var isSelected: Boolean = false) {
@PrimaryKey
var catNum: Int = tle.catnum
var name: String = tle.name
}
@@ -17,11 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
package com.rtbishop.look4sat.data
import com.github.amsacode.predict4java.SatPassTime
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.predict4kotlin.PassPredictor
import com.rtbishop.look4sat.utility.PassPredictor
data class SatPass(
val tle: TLE,
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
package com.rtbishop.look4sat.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@@ -34,5 +34,5 @@ data class Transmitter(
@SerializedName("downlink_high") val downlinkHigh: Long?,
@SerializedName("mode") val mode: String?,
@SerializedName("invert") val isInverted: Boolean,
@SerializedName("norad_cat_id") val noradCatId: Int
@SerializedName("norad_cat_id") val catNum: Int
)
@@ -1,45 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.di
import android.content.Context
import android.content.SharedPreferences
import android.location.LocationManager
import com.rtbishop.look4sat.utility.DataManager
import com.rtbishop.look4sat.utility.PrefsManager
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class UtilityModule {
@Singleton
@Provides
fun providePrefsManager(preferences: SharedPreferences): PrefsManager {
return PrefsManager(preferences)
}
@Singleton
@Provides
fun provideDataManager(locationManager: LocationManager, context: Context): DataManager {
return DataManager(locationManager, context)
}
}
@@ -19,11 +19,13 @@
package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.repo.Transmitter
import android.util.Log
import com.rtbishop.look4sat.data.Transmitter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
import java.io.InputStream
import java.io.SequenceInputStream
import java.util.*
@@ -32,20 +34,25 @@ import javax.inject.Inject
class RemoteDataSource @Inject constructor(
private val api: TransmittersApi,
private val client: OkHttpClient
) {
suspend fun getStreamForUrl(urlList: List<String>): InputStream {
) : RemoteSource {
override suspend fun fetchTleStream(urlList: List<String>): InputStream {
val streamTable = Hashtable<String, InputStream>()
withContext(Dispatchers.IO) {
urlList.forEach {
val request = Request.Builder().url(it).build()
val stream = client.newCall(request).execute().body()?.byteStream()!!
streamTable[it] = stream
try {
withContext(Dispatchers.IO) {
urlList.forEach {
val request = Request.Builder().url(it).build()
val stream = client.newCall(request).execute().body()?.byteStream()
streamTable[it] = stream
}
}
} catch (e: IOException) {
Log.w("remoteDataSource", e.toString())
}
return SequenceInputStream(streamTable.elements())
}
suspend fun fetchTransmittersList(): List<Transmitter> {
return api.fetchTransmittersList()
override suspend fun fetchTransmitters(): List<Transmitter> {
return api.fetchTransmitters()
}
}
@@ -0,0 +1,11 @@
package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.data.Transmitter
import java.io.InputStream
interface RemoteSource {
suspend fun fetchTleStream(urlList: List<String>): InputStream
suspend fun fetchTransmitters(): List<Transmitter>
}
@@ -19,11 +19,11 @@
package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.repo.Transmitter
import com.rtbishop.look4sat.data.Transmitter
import retrofit2.http.GET
interface TransmittersApi {
@GET("transmitters/")
suspend fun fetchTransmittersList(): List<Transmitter>
suspend fun fetchTransmitters(): List<Transmitter>
}
@@ -0,0 +1,58 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.persistence
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import javax.inject.Inject
class LocalDataSource @Inject constructor(
private val entriesDao: EntriesDao,
private val transmittersDao: TransmittersDao
) : LocalSource {
override suspend fun insertEntries(entries: List<SatEntry>) {
entriesDao.insertEntries(entries)
}
override suspend fun getAllEntries(): List<SatEntry> {
return entriesDao.getAllEntries()
}
override suspend fun getSelectedEntries(): List<SatEntry> {
return entriesDao.getSelectedEntries()
}
override suspend fun updateEntriesSelection(entries: List<SatEntry>) {
entriesDao.updateEntriesSelection(entries)
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun insertTransmitters(transmitters: List<Transmitter>) {
transmittersDao.insertTransmitters(transmitters)
}
override suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter> {
return transmittersDao.getTransmittersByCatNum(catNum)
}
}
@@ -0,0 +1,21 @@
package com.rtbishop.look4sat.persistence
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.Transmitter
interface LocalSource {
suspend fun insertEntries(entries: List<SatEntry>)
suspend fun getAllEntries(): List<SatEntry>
suspend fun getSelectedEntries(): List<SatEntry>
suspend fun updateEntriesSelection(entries: List<SatEntry>)
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun insertTransmitters(transmitters: List<Transmitter>)
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
}
@@ -17,14 +17,22 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.storage
package com.rtbishop.look4sat.persistence
import androidx.room.Database
import androidx.room.RoomDatabase
import com.rtbishop.look4sat.repo.Transmitter
import androidx.room.TypeConverters
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.persistence.dao.EntriesDao
import com.rtbishop.look4sat.persistence.dao.TransmittersDao
import com.rtbishop.look4sat.utility.Converters
@Database(entities = [Transmitter::class], version = 1, exportSchema = false)
abstract class TransmittersDb : RoomDatabase() {
@Database(entities = [Transmitter::class, SatEntry::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class SatelliteDb : RoomDatabase() {
abstract fun entriesDao(): EntriesDao
abstract fun transmittersDao(): TransmittersDao
}
@@ -17,20 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.storage
package com.rtbishop.look4sat.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.repo.Transmitter
import androidx.room.*
import com.rtbishop.look4sat.data.SatEntry
@Dao
interface TransmittersDao {
interface EntriesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<Transmitter>)
suspend fun insertEntries(entries: List<SatEntry>)
@Query("SELECT * FROM transmitters WHERE isAlive = 1 and noradCatId = :id")
suspend fun getTransmittersForSat(id: Int): List<Transmitter>
@Query("SELECT * FROM entries ORDER BY name ASC")
suspend fun getAllEntries(): List<SatEntry>
@Query("SELECT * FROM entries WHERE isSelected = 1 ORDER BY name ASC")
suspend fun getSelectedEntries(): List<SatEntry>
@Update
suspend fun updateEntriesSelection(entries: List<SatEntry>)
}
@@ -0,0 +1,17 @@
package com.rtbishop.look4sat.persistence.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.data.Transmitter
@Dao
interface TransmittersDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTransmitters(transmitters: List<Transmitter>)
@Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum")
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
}
@@ -0,0 +1,79 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
import android.util.Log
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.network.RemoteSource
import com.rtbishop.look4sat.persistence.LocalSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
import javax.inject.Inject
class DefaultRepository @Inject constructor(
private val localSource: LocalSource,
private val remoteSource: RemoteSource
) : Repository {
private val tag = "defaultRepository"
override suspend fun updateEntriesFrom(urlList: List<String>) {
val stream = remoteSource.fetchTleStream(urlList)
try {
withContext(Dispatchers.IO) {
val tleList = TLE.importSat(stream)
val entries = tleList.map { SatEntry(it) }
localSource.insertEntries(entries)
}
} catch (e: Exception) {
Log.w(tag, e.toString())
}
}
override suspend fun getAllEntries(): List<SatEntry> {
return localSource.getAllEntries()
}
override suspend fun getSelectedEntries(): List<SatEntry> {
return localSource.getSelectedEntries()
}
override suspend fun updateEntriesSelection(entries: List<SatEntry>) {
localSource.updateEntriesSelection(entries)
}
////////////////////////////////////////////////////////////////////////////////////////////////
override suspend fun updateTransmitters() {
try {
val list = remoteSource.fetchTransmitters()
localSource.insertTransmitters(list)
} catch (e: IOException) {
Log.w(tag, e.toString())
}
}
override suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter> {
return localSource.getTransmittersByCatNum(catNum)
}
}
@@ -1,42 +1,21 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
import com.rtbishop.look4sat.network.RemoteDataSource
import com.rtbishop.look4sat.storage.LocalDataSource
import java.io.InputStream
import javax.inject.Inject
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.Transmitter
class Repository @Inject constructor(
private val localSource: LocalDataSource,
private val remoteSource: RemoteDataSource
) {
suspend fun getStreamForUrl(urlList: List<String>): InputStream {
return remoteSource.getStreamForUrl(urlList)
}
interface Repository {
suspend fun updateTransmittersDatabase() {
localSource.insertTransmitters(remoteSource.fetchTransmittersList())
}
suspend fun updateEntriesFrom(urlList: List<String>)
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
return localSource.getTransmittersForSat(id)
}
suspend fun getAllEntries(): List<SatEntry>
suspend fun getSelectedEntries(): List<SatEntry>
suspend fun updateEntriesSelection(entries: List<SatEntry>)
////////////////////////////////////////////////////////////////////////////////////////////////
suspend fun updateTransmitters()
suspend fun getTransmittersByCatNum(catNum: Int): List<Transmitter>
}
@@ -1,22 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.repo
data class SatEntry(val id: Int, val name: String, var isSelected: Boolean = false)
@@ -1,34 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.storage
import com.rtbishop.look4sat.repo.Transmitter
import javax.inject.Inject
class LocalDataSource @Inject constructor(private val transmittersDao: TransmittersDao) {
suspend fun insertTransmitters(transmitters: List<Transmitter>) {
transmittersDao.insertTransmitters(transmitters)
}
suspend fun getTransmittersForSat(id: Int): List<Transmitter> {
return transmittersDao.getTransmittersForSat(id)
}
}
@@ -40,12 +40,14 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.databinding.ActivityMainBinding
import com.rtbishop.look4sat.databinding.DrawerHeaderBinding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
class MainActivity : AppCompatActivity() {
@@ -54,19 +56,25 @@ class MainActivity : AppCompatActivity() {
private val permGranted = PackageManager.PERMISSION_GRANTED
private val githubUrl = "https://github.com/rt-bishop/LookingSat"
@Inject
lateinit var viewModelFactory: ViewModelFactory
private lateinit var viewModel: SharedViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var drawerBinding: DrawerHeaderBinding
private lateinit var viewModel: MainViewModel
private lateinit var appBarConfig: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
drawerBinding = DrawerHeaderBinding.inflate(layoutInflater, mainBinding.navView, true)
setContentView(mainBinding.root)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
(application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(this, viewModelFactory).get(SharedViewModel::class.java)
setupComponents()
setupObservers()
setupDrawer()
@@ -108,10 +116,6 @@ class MainActivity : AppCompatActivity() {
}
private fun setupObservers() {
viewModel.getDebugMessage().observe(this, Observer { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
})
viewModel.getGSP().observe(this, Observer { gsp ->
drawerBinding.drawerLatValue.text =
String.format(getString(R.string.pat_location), gsp.latitude)
@@ -126,11 +130,11 @@ class MainActivity : AppCompatActivity() {
it.lockButton()
}
drawerBinding.drawerBtnTle.setOnClickListener {
viewModel.updateAndSaveTleFile()
viewModel.updateEntries()
it.lockButton()
}
drawerBinding.drawerBtnTrans.setOnClickListener {
viewModel.updateTransmittersDatabase()
viewModel.updateTransmitters()
it.lockButton()
}
drawerBinding.drawerBtnGithub.setOnClickListener {
@@ -157,7 +161,7 @@ class MainActivity : AppCompatActivity() {
ActivityCompat.requestPermissions(this, arrayOf(permLocation), permLocationCode)
}
} else {
viewModel.updateLocation()
viewModel.updatePosition()
}
}
@@ -165,7 +169,7 @@ class MainActivity : AppCompatActivity() {
when (reqCode) {
permLocationCode -> {
if (res.isNotEmpty() && res[0] == permGranted) {
viewModel.updateLocation()
viewModel.updatePosition()
}
}
}
@@ -0,0 +1,143 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
import androidx.lifecycle.*
import com.github.amsacode.predict4java.GroundStationPosition
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.repo.Repository
import com.rtbishop.look4sat.utility.PassPredictor
import com.rtbishop.look4sat.utility.PrefsManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SharedViewModel @Inject constructor(
private val prefsManager: PrefsManager,
private val repository: Repository
) : ViewModel() {
var isFirstLaunch = true
private var calculationJob: Job? = null
private val urlList = listOf("https://celestrak.com/NORAD/elements/active.txt")
private val _satPassList = MutableLiveData<MutableList<SatPass>>()
fun getSatPassList(): LiveData<MutableList<SatPass>> = _satPassList
private val _gsp = MutableLiveData(prefsManager.getPosition())
fun getGSP(): LiveData<GroundStationPosition> = _gsp
private val _isListRefreshing = MutableLiveData<Boolean>()
fun getRefreshing(): LiveData<Boolean> = _isListRefreshing
fun getRefreshRate() = prefsManager.getRefreshRate()
fun getHoursAhead() = prefsManager.getHoursAhead()
fun getMinElevation() = prefsManager.getMinElevation()
fun setPositionFromPref() = _gsp.postValue(prefsManager.getPosition())
fun getTransmittersForSat(id: Int) = liveData { emit(repository.getTransmittersByCatNum(id)) }
fun updateEntries() {
viewModelScope.launch {
val selected = repository.getSelectedEntries()
repository.updateEntriesFrom(urlList)
repository.updateEntriesSelection(selected)
}
}
fun updateEntriesSelection(entries: List<SatEntry>) {
viewModelScope.launch {
repository.updateEntriesSelection(entries)
calculatePasses()
}
}
fun updatePosition() {
prefsManager.getLastKnownPosition().let {
prefsManager.setPosition(it)
_gsp.postValue(it)
}
}
fun updateTransmitters() {
viewModelScope.launch(Dispatchers.IO) {
repository.updateTransmitters()
}
}
suspend fun getAllEntries(): List<SatEntry> {
return repository.getAllEntries()
}
fun calculatePasses() {
_isListRefreshing.postValue(true)
calculationJob?.cancel()
var passList = mutableListOf<SatPass>()
val dateNow = Date()
val gsp = getGSP().value ?: GroundStationPosition(0.0, 0.0, 0.0)
calculationJob = viewModelScope.launch(Dispatchers.Default) {
repository.getSelectedEntries().forEach {
passList.addAll(getPassesForEntries(it, dateNow, gsp))
}
passList = filterAndSortPasses(passList, dateNow, getHoursAhead())
_satPassList.postValue(passList)
_isListRefreshing.postValue(false)
}
}
fun setPassPrefs(hoursAhead: Int, minEl: Double) {
prefsManager.setHoursAhead(hoursAhead)
prefsManager.setMinElevation(minEl)
}
private fun getPassesForEntries(
entry: SatEntry,
dateNow: Date,
gsp: GroundStationPosition
): MutableList<SatPass> {
val passList = mutableListOf<SatPass>()
val predictor = PassPredictor(entry.tle, gsp)
val passes = predictor.getPasses(dateNow, getHoursAhead(), true)
passes.forEach { passList.add(SatPass(entry.tle, predictor, it)) }
return passList
}
private fun filterAndSortPasses(
passes: MutableList<SatPass>,
dateNow: Date,
hoursAhead: Int
): MutableList<SatPass> {
val dateFuture = Calendar.getInstance().let {
it.time = dateNow
it.add(Calendar.HOUR, hoursAhead)
it.time
}
passes.removeAll { it.pass.startTime.after(dateFuture) }
passes.removeAll { it.pass.endTime.before(dateNow) }
passes.removeAll { it.pass.maxEl < getMinElevation() }
passes.sortBy { it.pass.startTime }
return passes
}
}
@@ -23,10 +23,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.ItemSatEntryBinding
import com.rtbishop.look4sat.repo.SatEntry
internal class SatEntryAdapter(private var entriesList: MutableList<SatEntry>) :
class SatEntryAdapter(private var entries: MutableList<SatEntry>) :
RecyclerView.Adapter<SatEntryAdapter.SatEntryHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SatEntryHolder {
@@ -36,25 +36,27 @@ internal class SatEntryAdapter(private var entriesList: MutableList<SatEntry>) :
}
override fun onBindViewHolder(holder: SatEntryHolder, position: Int) {
holder.bind(entriesList[position])
holder.bind(entries[position])
}
override fun getItemCount(): Int {
return entriesList.size
return entries.size
}
fun setEntries(entries: MutableList<SatEntry>) {
entriesList = entries
fun setEntries(list: MutableList<SatEntry>) {
entries = list
notifyDataSetChanged()
}
internal inner class SatEntryHolder(itemView: View, private val binding: ItemSatEntryBinding) :
inner class SatEntryHolder(itemView: View, private val binding: ItemSatEntryBinding) :
RecyclerView.ViewHolder(itemView) {
fun bind(satEntry: SatEntry) {
binding.satEntryCheckbox.text = satEntry.name
binding.satEntryCheckbox.isChecked = satEntry.isSelected
itemView.setOnClickListener { satEntry.isSelected = binding.satEntryCheckbox.isChecked }
itemView.setOnClickListener {
satEntry.isSelected = binding.satEntryCheckbox.isChecked
}
}
}
}
@@ -25,16 +25,16 @@ import android.view.View
import android.view.ViewGroup
import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
import com.rtbishop.look4sat.repo.SatPass
import com.rtbishop.look4sat.ui.PassListFragmentDirections
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.fragments.PassListFragmentDirections
import java.text.SimpleDateFormat
import java.util.*
class SatPassAdapter(val viewModel: MainViewModel) :
class SatPassAdapter(val viewModel: SharedViewModel) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var satPassList = mutableListOf<SatPass>()
@@ -25,8 +25,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.databinding.ItemTransmitterBinding
import com.rtbishop.look4sat.repo.Transmitter
class TransmitterAdapter : RecyclerView.Adapter<TransmitterAdapter.TransmitterHolder>() {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.os.Bundle
import android.text.method.LinkMovementMethod
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.content.Context
import android.graphics.*
@@ -33,20 +33,26 @@ import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.Position
import com.github.amsacode.predict4java.SatPos
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapViewBinding
import com.rtbishop.look4sat.predict4kotlin.PassPredictor
import com.rtbishop.look4sat.repo.SatPass
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.utility.PassPredictor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
class MapViewFragment : Fragment(R.layout.fragment_map_view) {
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: MainViewModel
private lateinit var viewModel: SharedViewModel
private lateinit var mapView: MapView
private lateinit var predictor: PassPredictor
private lateinit var selectedSat: TLE
@@ -58,7 +64,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentMapViewBinding.bind(view)
mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
gsp = viewModel.getGSP().value ?: GroundStationPosition(0.0, 0.0, 0.0)
satPassList = viewModel.getSatPassList().value ?: emptyList()
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.os.Bundle
import android.os.CountDownTimer
@@ -30,68 +30,55 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentPassListBinding
import com.rtbishop.look4sat.repo.SatEntry
import com.rtbishop.look4sat.repo.SatPass
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.SatPassAdapter
import kotlinx.coroutines.launch
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class PassListFragment : Fragment(R.layout.fragment_pass_list) {
private lateinit var viewModel: MainViewModel
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var viewModel: SharedViewModel
private lateinit var satPassAdapter: SatPassAdapter
private lateinit var btnPassPrefs: ImageButton
private lateinit var aosTimer: CountDownTimer
private lateinit var aosTimerText: TextView
private lateinit var mainActivity: MainActivity
private var satPassList: MutableList<SatPass> = mutableListOf()
private var isTimerSet: Boolean = false
private var satPassList: MutableList<SatPass> = mutableListOf()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val fragmentBinding = FragmentPassListBinding.bind(view)
val binding = FragmentPassListBinding.bind(view)
mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
satPassAdapter = SatPassAdapter(viewModel)
aosTimerText = mainActivity.findViewById(R.id.toolbar_timer)
btnPassPrefs = mainActivity.findViewById(R.id.toolbar_filter)
setupComponents(fragmentBinding)
setupObservers(fragmentBinding)
setupObservers(binding)
setupComponents(binding)
if (viewModel.tleSelection.isNotEmpty() && viewModel.isFirstLaunch) {
viewModel.getPasses()
if (viewModel.isFirstLaunch) {
viewModel.calculatePasses()
viewModel.isFirstLaunch = false
}
}
private fun setupComponents(binding: FragmentPassListBinding) {
binding.recPassList.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = satPassAdapter
isVerticalScrollBarEnabled = false
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
satPassAdapter.setList(satPassList)
setTimer()
binding.refLayoutPassList.setProgressBackgroundColorSchemeResource(R.color.themeAccent)
binding.refLayoutPassList.setColorSchemeResources(R.color.backgroundDark)
binding.refLayoutPassList.setOnRefreshListener { viewModel.getPasses() }
btnPassPrefs.setOnClickListener {
showSatPassPrefsDialog(viewModel.getHoursAhead(), viewModel.getMinElevation())
}
binding.fabSatSelect.setOnClickListener {
showSelectSatDialog(viewModel.tleMainList, viewModel.tleSelection)
}
}
private fun setupObservers(binding: FragmentPassListBinding) {
viewModel.getSatPassList().observe(viewLifecycleOwner, Observer {
satPassList = it
@@ -110,6 +97,30 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
})
}
private fun setupComponents(binding: FragmentPassListBinding) {
binding.recPassList.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = satPassAdapter
isVerticalScrollBarEnabled = false
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
satPassAdapter.setList(satPassList)
setTimer()
binding.refLayoutPassList.setProgressBackgroundColorSchemeResource(R.color.themeAccent)
binding.refLayoutPassList.setColorSchemeResources(R.color.backgroundDark)
binding.refLayoutPassList.setOnRefreshListener { viewModel.calculatePasses() }
btnPassPrefs.setOnClickListener {
showSatPassPrefsDialog(viewModel.getHoursAhead(), viewModel.getMinElevation())
}
binding.fabSatSelect.setOnClickListener {
lifecycleScope.launch {
val list = viewModel.getAllEntries() as MutableList
showSelectSatDialog(list)
}
}
}
private fun showSatPassPrefsDialog(hoursAhead: Int, minEl: Double) {
val satPassPrefView = View.inflate(mainActivity, R.layout.dialog_pass_pref, null)
val etHoursAhead = satPassPrefView.findViewById<EditText>(R.id.pref_et_hoursAhead)
@@ -142,7 +153,7 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
}
else -> {
viewModel.setPassPrefs(hours, elevation)
viewModel.getPasses()
viewModel.calculatePasses()
}
}
} else Toast.makeText(
@@ -160,31 +171,17 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
.show()
}
private fun showSelectSatDialog(tleMainList: List<TLE>, selection: MutableList<Int>) {
if (tleMainList.isEmpty()) {
Toast.makeText(mainActivity, getString(R.string.err_update_tle), Toast.LENGTH_SHORT)
.show()
} else {
val entriesList = mutableListOf<SatEntry>().apply {
tleMainList.withIndex().forEach {
this.add(SatEntry(it.index, it.value.name))
}
private fun showSelectSatDialog(tleMainList: MutableList<SatEntry>) {
val listener = object : SatEntryDialogFragment.EntriesSubmitListener {
override fun onEntriesSubmit(entries: MutableList<SatEntry>) {
viewModel.updateEntriesSelection(entries)
}
val listener = object : SatEntryDialog.EntriesSubmitListener {
override fun onEntriesSubmit(list: MutableList<Int>) {
viewModel.updateAndSaveSelectionList(list)
viewModel.getPasses()
}
}
val dialogFragment = SatEntryDialog().apply {
setEntriesList(entriesList)
setSelectionList(selection)
setEntriesListener(listener)
}
dialogFragment.show(mainActivity.supportFragmentManager, "SatEntryDialog")
}
val dialogFragment = SatEntryDialogFragment(tleMainList).apply {
setEntriesListener(listener)
}
dialogFragment.show(mainActivity.supportFragmentManager, "SatEntryDialogFragment")
}
private fun setTimer() {
@@ -229,7 +226,7 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
val millisBeforeEnd = lastPass.pass.endTime.time.minus(timeNow.time)
aosTimer = object : CountDownTimer(millisBeforeEnd, 1000) {
override fun onFinish() {
viewModel.getPasses()
viewModel.calculatePasses()
}
override fun onTick(millisUntilFinished: Long) {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.content.Context
import android.graphics.Canvas
@@ -32,43 +32,65 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.amsacode.predict4java.SatPos
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentPolarViewBinding
import com.rtbishop.look4sat.repo.SatPass
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.TransmitterAdapter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import kotlin.math.cos
import kotlin.math.sin
class PolarViewFragment : Fragment(R.layout.fragment_polar_view) {
@Inject
lateinit var modelFactory: ViewModelFactory
private val args: PolarViewFragmentArgs by navArgs()
private lateinit var viewModel: MainViewModel
private lateinit var viewModel: SharedViewModel
private lateinit var satPass: SatPass
private lateinit var polarView: PolarView
private lateinit var transmitterAdapter: TransmitterAdapter
private lateinit var mainActivity: MainActivity
private var transmitterAdapter: TransmitterAdapter = TransmitterAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentPolarViewBinding.bind(view)
mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
viewModel.getSatPassList().value?.let {
val refreshRate = viewModel.getRefreshRate()
satPass = it[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
(mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
val refreshRate = viewModel.getRefreshRate()
polarView = PolarView(mainActivity, refreshRate, binding)
binding.framePolar.addView(polarView)
setupTransRecycler(binding)
refreshView()
binding.recPolar.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
}
viewModel.getSatPassList()
.observe(viewLifecycleOwner, androidx.lifecycle.Observer { mutableList ->
satPass = mutableList[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
polarView = PolarView(mainActivity, refreshRate, binding)
binding.framePolar.addView(polarView)
refreshView()
viewModel.getTransmittersForSat(satPass.tle.catnum)
.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
if (it.isNotEmpty()) {
transmitterAdapter.setList(it)
transmitterAdapter.notifyDataSetChanged()
binding.recPolar.visibility = View.VISIBLE
binding.tvPolarNoTrans.visibility = View.INVISIBLE
} else {
binding.recPolar.visibility = View.INVISIBLE
binding.tvPolarNoTrans.visibility = View.VISIBLE
}
})
})
}
private fun refreshView() {
@@ -80,24 +102,6 @@ class PolarViewFragment : Fragment(R.layout.fragment_polar_view) {
}
}
private fun setupTransRecycler(binding: FragmentPolarViewBinding) {
transmitterAdapter = TransmitterAdapter()
binding.recPolar.apply {
layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
}
lifecycleScope.launch {
val transList = viewModel.getTransmittersForSat(satPass.tle.catnum)
if (transList.isNotEmpty()) {
transmitterAdapter.setList(transList)
transmitterAdapter.notifyDataSetChanged()
} else {
binding.recPolar.visibility = View.INVISIBLE
binding.tvPolarNoTrans.visibility = View.VISIBLE
}
}
}
inner class PolarView(
context: Context,
private val updateFreq: Long,
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.content.SharedPreferences
import android.os.Bundle
@@ -26,14 +26,15 @@ import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.ui.SharedViewModel
class SettingsFragment : PreferenceFragmentCompat(),
class PreferencesFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var mainActivity: MainActivity
private lateinit var viewModel: MainViewModel
private lateinit var viewModel: SharedViewModel
private lateinit var keyLat: String
private lateinit var keyLon: String
private lateinit var keyAlt: String
@@ -44,7 +45,7 @@ class SettingsFragment : PreferenceFragmentCompat(),
val provider = EditTextPreference.SimpleSummaryProvider.getInstance()
mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java)
viewModel = ViewModelProvider(mainActivity).get(SharedViewModel::class.java)
keyLat = mainActivity.getString(R.string.pref_lat_key)
keyLon = mainActivity.getString(R.string.pref_lon_key)
@@ -139,7 +140,7 @@ class SettingsFragment : PreferenceFragmentCompat(),
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == keyLat || key == keyLon || key == keyAlt) viewModel.setGroundStationPosition()
if (key == keyLat || key == keyLon || key == keyAlt) viewModel.setPositionFromPref()
}
override fun onResume() {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.ui
package com.rtbishop.look4sat.ui.fragments
import android.app.Dialog
import android.os.Bundle
@@ -26,18 +26,16 @@ import android.view.Window
import android.widget.SearchView
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.DialogSatEntryBinding
import com.rtbishop.look4sat.repo.SatEntry
import com.rtbishop.look4sat.ui.adapters.SatEntryAdapter
import java.util.*
class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener {
class SatEntryDialogFragment(private var entries: MutableList<SatEntry>) :
AppCompatDialogFragment(), SearchView.OnQueryTextListener {
private lateinit var satEntryAdapter: SatEntryAdapter
private lateinit var entriesListener: EntriesSubmitListener
private var entriesList = mutableListOf<SatEntry>()
private var selectionList = mutableListOf<Int>()
private val entryAdapter = SatEntryAdapter(entries)
private var selectAllToggle = true
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -51,18 +49,15 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
)
}
entriesList = checkSelectedEntries(entriesList, selectionList)
satEntryAdapter = SatEntryAdapter(entriesList)
binding.dialogSearch.apply {
setOnQueryTextListener(this@SatEntryDialog)
setOnQueryTextListener(this@SatEntryDialogFragment)
onActionViewExpanded()
clearFocus()
}
binding.dialogRecycler.apply {
layoutManager = LinearLayoutManager(activity)
adapter = satEntryAdapter
adapter = entryAdapter
}
binding.dialogBtnPositive.setOnClickListener { onPositiveClicked() }
@@ -72,32 +67,27 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
return satEntryDialog
}
fun setEntriesList(list: MutableList<SatEntry>): SatEntryDialog {
entriesList = list
return this
}
fun setSelectionList(list: MutableList<Int>): SatEntryDialog {
selectAllToggle = list.isEmpty()
selectionList = list
return this
}
fun setEntriesListener(listener: EntriesSubmitListener): SatEntryDialog {
fun setEntriesListener(listener: EntriesSubmitListener): SatEntryDialogFragment {
entriesListener = listener
return this
}
private fun checkSelectedEntries(entries: MutableList<SatEntry>, selection: MutableList<Int>)
: MutableList<SatEntry> {
selection.forEach { entries[it].isSelected = true }
return entries
private fun filterEntries(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
if (query.isEmpty()) return list
return try {
filterByCatNum(list, query.toInt())
} catch (e: NumberFormatException) {
filterByName(list, query)
}
}
private fun filterEntries(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
val searchQuery = query.toLowerCase(Locale.getDefault())
if ((searchQuery == "") or searchQuery.isEmpty()) return list
private fun filterByCatNum(list: MutableList<SatEntry>, catNum: Int): MutableList<SatEntry> {
val filteredList = list.filter { it.catNum == catNum }
return filteredList as MutableList<SatEntry>
}
private fun filterByName(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
val searchQuery = query.toLowerCase(Locale.getDefault())
val filteredList = mutableListOf<SatEntry>()
list.forEach {
val name = it.name.toLowerCase(Locale.getDefault())
@@ -107,10 +97,7 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
}
private fun onPositiveClicked() {
val listToSubmit = mutableListOf<Int>().apply {
entriesList.forEach { if (it.isSelected) this.add(it.id) }
}
entriesListener.onEntriesSubmit(listToSubmit)
entriesListener.onEntriesSubmit(entries)
dismiss()
}
@@ -120,20 +107,19 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
private fun onNeutralClicked() {
selectAllToggle = if (selectAllToggle) {
entriesList.forEach { it.isSelected = true }
satEntryAdapter.setEntries(entriesList)
entries.forEach { it.isSelected = true }
entryAdapter.setEntries(entries)
false
} else {
entriesList.forEach { it.isSelected = false }
satEntryAdapter.setEntries(entriesList)
entries.forEach { it.isSelected = false }
entryAdapter.setEntries(entries)
true
}
}
override fun onQueryTextChange(newText: String): Boolean {
entriesList = checkSelectedEntries(entriesList, selectionList)
val filteredList = filterEntries(entriesList, newText)
satEntryAdapter.setEntries(filteredList)
val filteredList = filterEntries(entries, newText)
entryAdapter.setEntries(filteredList)
return false
}
@@ -142,6 +128,6 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
}
interface EntriesSubmitListener {
fun onEntriesSubmit(list: MutableList<Int>)
fun onEntriesSubmit(entries: MutableList<SatEntry>)
}
}
@@ -0,0 +1,19 @@
package com.rtbishop.look4sat.utility
import androidx.room.TypeConverter
import com.github.amsacode.predict4java.TLE
import com.google.gson.Gson
class Converters {
private val gSon = Gson()
@TypeConverter
fun tleToString(tle: TLE): String {
return gSon.toJson(tle)
}
@TypeConverter
fun tleFromString(string: String): TLE {
return gSon.fromJson(string, TLE::class.java)
}
}
@@ -1,115 +0,0 @@
/*
* Look4Sat. Amateur radio and weather satellite tracker and passes predictor for Android.
* Copyright (C) 2019, 2020 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 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.rtbishop.look4sat.utility
import android.content.Context
import android.location.LocationManager
import android.util.Log
import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.R
import java.io.FileNotFoundException
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import javax.inject.Inject
class DataManager @Inject constructor(
private val locationManager: LocationManager,
private val context: Context
) {
private val tag = "DataManager"
private val tleMainListFileName = "tleFile.txt"
private val tleSelectionFileName = "tleSelection"
fun getLastKnownLocation(): GroundStationPosition? {
val passiveProvider = LocationManager.PASSIVE_PROVIDER
var groundStationPosition: GroundStationPosition? = null
return try {
val location = locationManager.getLastKnownLocation(passiveProvider)
location?.let {
groundStationPosition =
GroundStationPosition(it.latitude, it.longitude, it.altitude)
}
groundStationPosition
} catch (e: SecurityException) {
Log.w(tag, "No permissions")
null
}
}
fun saveSelectionList(selectionList: MutableList<Int>) {
try {
val fileOutputStream =
context.openFileOutput(tleSelectionFileName, Context.MODE_PRIVATE)
ObjectOutputStream(fileOutputStream).apply {
writeObject(selectionList)
flush()
close()
}
} catch (exception: IOException) {
Log.w(tag, "Can't save selection list")
}
}
fun saveTleList(tleList: MutableList<TLE>) {
try {
val fileOutStream = context.openFileOutput(tleMainListFileName, Context.MODE_PRIVATE)
ObjectOutputStream(fileOutStream).apply {
writeObject(tleList)
flush()
close()
}
} catch (exception: IOException) {
Log.w(tag, "Can't save TLE list")
}
}
@Suppress("UNCHECKED_CAST")
fun loadTleList(): List<TLE> {
return try {
val tleStream = context.openFileInput(tleMainListFileName)
val tleList = ObjectInputStream(tleStream).readObject()
tleList as List<TLE>
} catch (exception: FileNotFoundException) {
Log.w(tag, context.getString(R.string.err_no_tle_file))
emptyList()
} catch (exception: IOException) {
Log.w(tag, exception.toString())
emptyList()
}
}
@Suppress("UNCHECKED_CAST")
fun loadSelectionList(): MutableList<Int> {
return try {
val selectionStream = context.openFileInput(tleSelectionFileName)
val selectionList = ObjectInputStream(selectionStream).readObject()
selectionList as MutableList<Int>
} catch (exception: FileNotFoundException) {
Log.w(tag, context.getString(R.string.err_no_selection_file))
mutableListOf()
} catch (exception: IOException) {
Log.w(tag, exception.toString())
mutableListOf()
}
}
}
@@ -35,7 +35,7 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, visit http://www.fsf.org/
*/
package com.rtbishop.look4sat.predict4kotlin
package com.rtbishop.look4sat.utility
import com.github.amsacode.predict4java.*
import java.util.*
@@ -253,14 +253,18 @@ class PassPredictor(private val tle: TLE, private val qth: GroundStationPosition
var polePassed = deadSpotNone
val az1 = prevPos.azimuth / twoPi * 360.0
val az2 = satPos.azimuth / twoPi * 360.0
if (az1 > az2) { // we may be moving from 350 or greater through north
if (az1 > az2) {
// we may be moving from 350 or greater through north
if (az1 > 350 && az2 < 10) polePassed = north
else { // we may be moving from 190 or greater through south
else {
// we may be moving from 190 or greater through south
if (az1 > 180 && az2 < 180) polePassed = south
}
} else { // we may be moving from 10 or less through north
} else {
// we may be moving from 10 or less through north
if (az1 < 10 && az2 > 350) polePassed = north
else { // we may be moving from 170 or more through south
else {
// we may be moving from 170 or more through south
if (az1 < 180 && az2 > 180) polePassed = south
}
}
@@ -270,4 +274,4 @@ class PassPredictor(private val tle: TLE, private val qth: GroundStationPosition
private fun threeQuarterOrbitMinutes(): Int {
return (24.0 * 60.0 / tle.meanmo * 0.75).toInt()
}
}
}
@@ -20,35 +20,44 @@
package com.rtbishop.look4sat.utility
import android.content.SharedPreferences
import android.location.LocationManager
import android.util.Log
import androidx.core.content.edit
import com.github.amsacode.predict4java.GroundStationPosition
import javax.inject.Inject
class PrefsManager @Inject constructor(private val preferences: SharedPreferences) {
class PrefsManager @Inject constructor(
private val preferences: SharedPreferences,
private val locationManager: LocationManager
) {
private val tag = "prefsManager"
private val keyHoursAhead = "hoursAhead"
private val keyMinElevation = "minEl"
private val keyLatitude = "latitude"
private val keyLongitude = "longitude"
private val keyAltitude = "altitude"
private val keyRefreshRate = "rate"
private val defaultHoursAhead = 8
private val defaultMinEl = 16.0
private val defaultRefreshRate = "3000"
private val defaultGSP = "0.0"
fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, 8)
return preferences.getInt(keyHoursAhead, defaultHoursAhead)
}
fun getMinElevation(): Double {
return preferences.getDouble(keyMinElevation, 16.0)
return preferences.getDouble(keyMinElevation, defaultMinEl)
}
fun getRefreshRate(): Long {
return preferences.getString(keyRefreshRate, "3000")!!.toLong()
return preferences.getString(keyRefreshRate, defaultRefreshRate)!!.toLong()
}
fun getGroundStationPosition(): GroundStationPosition {
val lat = preferences.getString(keyLatitude, "0.0")!!.toDouble()
val lon = preferences.getString(keyLongitude, "0.0")!!.toDouble()
val alt = preferences.getString(keyAltitude, "0.0")!!.toDouble()
fun getPosition(): GroundStationPosition {
val lat = preferences.getString(keyLatitude, defaultGSP)!!.toDouble()
val lon = preferences.getString(keyLongitude, defaultGSP)!!.toDouble()
val alt = preferences.getString(keyAltitude, defaultGSP)!!.toDouble()
return GroundStationPosition(lat, lon, alt)
}
@@ -73,7 +82,7 @@ class PrefsManager @Inject constructor(private val preferences: SharedPreference
}
}
fun setGroundStationPosition(gsp: GroundStationPosition) {
fun setPosition(gsp: GroundStationPosition) {
preferences.edit {
putString(keyLatitude, gsp.latitude.toString())
putString(keyLongitude, gsp.longitude.toString())
@@ -82,6 +91,22 @@ class PrefsManager @Inject constructor(private val preferences: SharedPreference
}
}
fun getLastKnownPosition(): GroundStationPosition {
val provider = LocationManager.PASSIVE_PROVIDER
var gsp = GroundStationPosition(0.0, 0.0, 0.0)
return try {
val location = locationManager.getLastKnownLocation(provider)
location?.let {
gsp =
GroundStationPosition(it.latitude, it.longitude, it.altitude)
}
gsp
} catch (e: SecurityException) {
Log.w(tag, "No permissions")
gsp
}
}
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
putLong(key, double.toRawBits())
}
+1 -1
View File
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.AboutFragment">
tools:context=".ui.fragments.AboutFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MapViewFragment">
tools:context=".ui.fragments.MapViewFragment">
<FrameLayout
android:id="@+id/frameMap"
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.PolarViewFragment">
tools:context=".ui.fragments.PolarViewFragment">
<FrameLayout
android:id="@+id/frame_polar"
+5 -5
View File
@@ -26,7 +26,7 @@
<fragment
android:id="@+id/nav_pass_list"
android:name="com.rtbishop.look4sat.ui.PassListFragment"
android:name="com.rtbishop.look4sat.ui.fragments.PassListFragment"
android:label="@string/menu_pass_list"
tools:layout="@layout/fragment_pass_list">
<action
@@ -40,7 +40,7 @@
<fragment
android:id="@+id/nav_polar_view"
android:name="com.rtbishop.look4sat.ui.PolarViewFragment"
android:name="com.rtbishop.look4sat.ui.fragments.PolarViewFragment"
android:label="@string/menu_polar_view"
tools:layout="@layout/fragment_polar_view">
<argument
@@ -50,18 +50,18 @@
<fragment
android:id="@+id/nav_map_view"
android:name="com.rtbishop.look4sat.ui.MapViewFragment"
android:name="com.rtbishop.look4sat.ui.fragments.MapViewFragment"
android:label="@string/menu_map_view"
tools:layout="@layout/fragment_map_view" />
<fragment
android:id="@+id/nav_settings"
android:name="com.rtbishop.look4sat.ui.SettingsFragment"
android:name="com.rtbishop.look4sat.ui.fragments.PreferencesFragment"
android:label="@string/menu_settings" />
<fragment
android:id="@+id/nav_about"
android:name="com.rtbishop.look4sat.ui.AboutFragment"
android:name="com.rtbishop.look4sat.ui.fragments.AboutFragment"
android:label="@string/menu_about"
tools:layout="@layout/fragment_about" />