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

+1
View File
@@ -72,6 +72,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle"
// room // room
implementation "androidx.room:room-runtime:$room" implementation "androidx.room:room-runtime:$room"
@@ -20,8 +20,8 @@
package com.rtbishop.look4sat package com.rtbishop.look4sat
import android.app.Application import android.app.Application
import com.rtbishop.look4sat.di.AppComponent import com.rtbishop.look4sat.dagger.AppComponent
import com.rtbishop.look4sat.di.DaggerAppComponent import com.rtbishop.look4sat.dagger.DaggerAppComponent
class Look4SatApp : Application() { 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. * 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 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.BindsInstance
import dagger.Component import dagger.Component
import javax.inject.Singleton import javax.inject.Singleton
@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 { interface AppComponent {
@Component.Factory @Component.Factory
@@ -34,5 +41,8 @@ interface AppComponent {
fun create(@BindsInstance context: Context): 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. * 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 com.rtbishop.look4sat.network.TransmittersApi
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@@ -30,6 +32,18 @@ import javax.inject.Singleton
@Module @Module
class NetworkModule { class NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
@Singleton
@Provides
fun provideRemoteDataSource(api: TransmittersApi, client: OkHttpClient): RemoteSource {
return RemoteDataSource(api, client)
}
@Singleton @Singleton
@Provides @Provides
fun provideTransmittersApi(): TransmittersApi { fun provideTransmittersApi(): TransmittersApi {
@@ -39,10 +53,4 @@ class NetworkModule {
.build() .build()
.create(TransmittersApi::class.java) .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. * 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.Context
import androidx.room.Room import androidx.room.Room
import com.rtbishop.look4sat.storage.TransmittersDao import com.rtbishop.look4sat.persistence.LocalDataSource
import com.rtbishop.look4sat.storage.TransmittersDb 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.Module
import dagger.Provides import dagger.Provides
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
class StorageModule { class PersistenceModule {
@Singleton @Singleton
@Provides @Provides
fun provideTransmittersDb(context: Context): TransmittersDb { fun provideEntriesDao(db: SatelliteDb): EntriesDao {
return Room.databaseBuilder(context, TransmittersDb::class.java, "transmitters") return db.entriesDao()
.build()
} }
@Singleton @Singleton
@Provides @Provides
fun provideTransmittersDao(db: TransmittersDb): TransmittersDao { fun provideTransmittersDao(db: SatelliteDb): TransmittersDao {
return db.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. * 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.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.location.LocationManager import android.location.LocationManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.rtbishop.look4sat.utility.PrefsManager
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
class AndroidModule { class UtilityModule {
@Singleton @Singleton
@Provides @Provides
@@ -41,4 +42,13 @@ class AndroidModule {
fun provideLocationManager(context: Context): LocationManager { fun provideLocationManager(context: Context): LocationManager {
return context.getSystemService(Context.LOCATION_SERVICE) as 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. * 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.SatPassTime
import com.github.amsacode.predict4java.TLE import com.github.amsacode.predict4java.TLE
import com.rtbishop.look4sat.predict4kotlin.PassPredictor import com.rtbishop.look4sat.utility.PassPredictor
data class SatPass( data class SatPass(
val tle: TLE, val tle: TLE,
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@@ -34,5 +34,5 @@ data class Transmitter(
@SerializedName("downlink_high") val downlinkHigh: Long?, @SerializedName("downlink_high") val downlinkHigh: Long?,
@SerializedName("mode") val mode: String?, @SerializedName("mode") val mode: String?,
@SerializedName("invert") val isInverted: Boolean, @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 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.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.SequenceInputStream import java.io.SequenceInputStream
import java.util.* import java.util.*
@@ -32,20 +34,25 @@ import javax.inject.Inject
class RemoteDataSource @Inject constructor( class RemoteDataSource @Inject constructor(
private val api: TransmittersApi, private val api: TransmittersApi,
private val client: OkHttpClient private val client: OkHttpClient
) { ) : RemoteSource {
suspend fun getStreamForUrl(urlList: List<String>): InputStream {
override suspend fun fetchTleStream(urlList: List<String>): InputStream {
val streamTable = Hashtable<String, InputStream>() val streamTable = Hashtable<String, InputStream>()
withContext(Dispatchers.IO) { try {
urlList.forEach { withContext(Dispatchers.IO) {
val request = Request.Builder().url(it).build() urlList.forEach {
val stream = client.newCall(request).execute().body()?.byteStream()!! val request = Request.Builder().url(it).build()
streamTable[it] = stream val stream = client.newCall(request).execute().body()?.byteStream()
streamTable[it] = stream
}
} }
} catch (e: IOException) {
Log.w("remoteDataSource", e.toString())
} }
return SequenceInputStream(streamTable.elements()) return SequenceInputStream(streamTable.elements())
} }
suspend fun fetchTransmittersList(): List<Transmitter> { override suspend fun fetchTransmitters(): List<Transmitter> {
return api.fetchTransmittersList() 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 package com.rtbishop.look4sat.network
import com.rtbishop.look4sat.repo.Transmitter import com.rtbishop.look4sat.data.Transmitter
import retrofit2.http.GET import retrofit2.http.GET
interface TransmittersApi { interface TransmittersApi {
@GET("transmitters/") @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. * 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.Database
import androidx.room.RoomDatabase 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) @Database(entities = [Transmitter::class, SatEntry::class], version = 1, exportSchema = false)
abstract class TransmittersDb : RoomDatabase() { @TypeConverters(Converters::class)
abstract class SatelliteDb : RoomDatabase() {
abstract fun entriesDao(): EntriesDao
abstract fun transmittersDao(): TransmittersDao abstract fun transmittersDao(): TransmittersDao
} }
@@ -17,20 +17,23 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.*
import androidx.room.Insert import com.rtbishop.look4sat.data.SatEntry
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.rtbishop.look4sat.repo.Transmitter
@Dao @Dao
interface TransmittersDao { interface EntriesDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @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") @Query("SELECT * FROM entries ORDER BY name ASC")
suspend fun getTransmittersForSat(id: Int): List<Transmitter> 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 package com.rtbishop.look4sat.repo
import com.rtbishop.look4sat.network.RemoteDataSource import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.storage.LocalDataSource import com.rtbishop.look4sat.data.Transmitter
import java.io.InputStream
import javax.inject.Inject
class Repository @Inject constructor( interface Repository {
private val localSource: LocalDataSource,
private val remoteSource: RemoteDataSource
) {
suspend fun getStreamForUrl(urlList: List<String>): InputStream {
return remoteSource.getStreamForUrl(urlList)
}
suspend fun updateTransmittersDatabase() { suspend fun updateEntriesFrom(urlList: List<String>)
localSource.insertTransmitters(remoteSource.fetchTransmittersList())
}
suspend fun getTransmittersForSat(id: Int): List<Transmitter> { suspend fun getAllEntries(): List<SatEntry>
return localSource.getTransmittersForSat(id)
} 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.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.MainViewModel import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.databinding.ActivityMainBinding import com.rtbishop.look4sat.databinding.ActivityMainBinding
import com.rtbishop.look4sat.databinding.DrawerHeaderBinding import com.rtbishop.look4sat.databinding.DrawerHeaderBinding
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -54,19 +56,25 @@ class MainActivity : AppCompatActivity() {
private val permGranted = PackageManager.PERMISSION_GRANTED private val permGranted = PackageManager.PERMISSION_GRANTED
private val githubUrl = "https://github.com/rt-bishop/LookingSat" 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 mainBinding: ActivityMainBinding
private lateinit var drawerBinding: DrawerHeaderBinding private lateinit var drawerBinding: DrawerHeaderBinding
private lateinit var viewModel: MainViewModel
private lateinit var appBarConfig: AppBarConfiguration private lateinit var appBarConfig: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme) setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater) mainBinding = ActivityMainBinding.inflate(layoutInflater)
drawerBinding = DrawerHeaderBinding.inflate(layoutInflater, mainBinding.navView, true) drawerBinding = DrawerHeaderBinding.inflate(layoutInflater, mainBinding.navView, true)
setContentView(mainBinding.root) setContentView(mainBinding.root)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 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() setupComponents()
setupObservers() setupObservers()
setupDrawer() setupDrawer()
@@ -108,10 +116,6 @@ class MainActivity : AppCompatActivity() {
} }
private fun setupObservers() { private fun setupObservers() {
viewModel.getDebugMessage().observe(this, Observer { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
})
viewModel.getGSP().observe(this, Observer { gsp -> viewModel.getGSP().observe(this, Observer { gsp ->
drawerBinding.drawerLatValue.text = drawerBinding.drawerLatValue.text =
String.format(getString(R.string.pat_location), gsp.latitude) String.format(getString(R.string.pat_location), gsp.latitude)
@@ -126,11 +130,11 @@ class MainActivity : AppCompatActivity() {
it.lockButton() it.lockButton()
} }
drawerBinding.drawerBtnTle.setOnClickListener { drawerBinding.drawerBtnTle.setOnClickListener {
viewModel.updateAndSaveTleFile() viewModel.updateEntries()
it.lockButton() it.lockButton()
} }
drawerBinding.drawerBtnTrans.setOnClickListener { drawerBinding.drawerBtnTrans.setOnClickListener {
viewModel.updateTransmittersDatabase() viewModel.updateTransmitters()
it.lockButton() it.lockButton()
} }
drawerBinding.drawerBtnGithub.setOnClickListener { drawerBinding.drawerBtnGithub.setOnClickListener {
@@ -157,7 +161,7 @@ class MainActivity : AppCompatActivity() {
ActivityCompat.requestPermissions(this, arrayOf(permLocation), permLocationCode) ActivityCompat.requestPermissions(this, arrayOf(permLocation), permLocationCode)
} }
} else { } else {
viewModel.updateLocation() viewModel.updatePosition()
} }
} }
@@ -165,7 +169,7 @@ class MainActivity : AppCompatActivity() {
when (reqCode) { when (reqCode) {
permLocationCode -> { permLocationCode -> {
if (res.isNotEmpty() && res[0] == permGranted) { 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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.ItemSatEntryBinding 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>() { RecyclerView.Adapter<SatEntryAdapter.SatEntryHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 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) { override fun onBindViewHolder(holder: SatEntryHolder, position: Int) {
holder.bind(entriesList[position]) holder.bind(entries[position])
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return entriesList.size return entries.size
} }
fun setEntries(entries: MutableList<SatEntry>) { fun setEntries(list: MutableList<SatEntry>) {
entriesList = entries entries = list
notifyDataSetChanged() notifyDataSetChanged()
} }
internal inner class SatEntryHolder(itemView: View, private val binding: ItemSatEntryBinding) : inner class SatEntryHolder(itemView: View, private val binding: ItemSatEntryBinding) :
RecyclerView.ViewHolder(itemView) { RecyclerView.ViewHolder(itemView) {
fun bind(satEntry: SatEntry) { fun bind(satEntry: SatEntry) {
binding.satEntryCheckbox.text = satEntry.name binding.satEntryCheckbox.text = satEntry.name
binding.satEntryCheckbox.isChecked = satEntry.isSelected 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 android.view.ViewGroup
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.ItemPassGeoBinding import com.rtbishop.look4sat.databinding.ItemPassGeoBinding
import com.rtbishop.look4sat.databinding.ItemPassLeoBinding import com.rtbishop.look4sat.databinding.ItemPassLeoBinding
import com.rtbishop.look4sat.repo.SatPass import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.PassListFragmentDirections import com.rtbishop.look4sat.ui.fragments.PassListFragmentDirections
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class SatPassAdapter(val viewModel: MainViewModel) : class SatPassAdapter(val viewModel: SharedViewModel) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var satPassList = mutableListOf<SatPass>() private var satPassList = mutableListOf<SatPass>()
@@ -25,8 +25,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.Transmitter
import com.rtbishop.look4sat.databinding.ItemTransmitterBinding import com.rtbishop.look4sat.databinding.ItemTransmitterBinding
import com.rtbishop.look4sat.repo.Transmitter
class TransmitterAdapter : RecyclerView.Adapter<TransmitterAdapter.TransmitterHolder>() { class TransmitterAdapter : RecyclerView.Adapter<TransmitterAdapter.TransmitterHolder>() {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.Bundle
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.content.Context
import android.graphics.* import android.graphics.*
@@ -33,20 +33,26 @@ import com.github.amsacode.predict4java.GroundStationPosition
import com.github.amsacode.predict4java.Position import com.github.amsacode.predict4java.Position
import com.github.amsacode.predict4java.SatPos import com.github.amsacode.predict4java.SatPos
import com.github.amsacode.predict4java.TLE 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.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentMapViewBinding import com.rtbishop.look4sat.databinding.FragmentMapViewBinding
import com.rtbishop.look4sat.predict4kotlin.PassPredictor import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.repo.SatPass import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.utility.PassPredictor
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
class MapViewFragment : Fragment(R.layout.fragment_map_view) { class MapViewFragment : Fragment(R.layout.fragment_map_view) {
@Inject
lateinit var modelFactory: ViewModelFactory
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var viewModel: MainViewModel private lateinit var viewModel: SharedViewModel
private lateinit var mapView: MapView private lateinit var mapView: MapView
private lateinit var predictor: PassPredictor private lateinit var predictor: PassPredictor
private lateinit var selectedSat: TLE private lateinit var selectedSat: TLE
@@ -58,7 +64,8 @@ class MapViewFragment : Fragment(R.layout.fragment_map_view) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val binding = FragmentMapViewBinding.bind(view) val binding = FragmentMapViewBinding.bind(view)
mainActivity = activity as MainActivity 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) gsp = viewModel.getGSP().value ?: GroundStationPosition(0.0, 0.0, 0.0)
satPassList = viewModel.getSatPassList().value ?: emptyList() satPassList = viewModel.getSatPassList().value ?: emptyList()
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
@@ -30,68 +30,55 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.github.amsacode.predict4java.TLE import com.rtbishop.look4sat.Look4SatApp
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.R 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.databinding.FragmentPassListBinding
import com.rtbishop.look4sat.repo.SatEntry import com.rtbishop.look4sat.ui.MainActivity
import com.rtbishop.look4sat.repo.SatPass import com.rtbishop.look4sat.ui.SharedViewModel
import com.rtbishop.look4sat.ui.adapters.SatPassAdapter import com.rtbishop.look4sat.ui.adapters.SatPassAdapter
import kotlinx.coroutines.launch
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
class PassListFragment : Fragment(R.layout.fragment_pass_list) { 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 satPassAdapter: SatPassAdapter
private lateinit var btnPassPrefs: ImageButton private lateinit var btnPassPrefs: ImageButton
private lateinit var aosTimer: CountDownTimer private lateinit var aosTimer: CountDownTimer
private lateinit var aosTimerText: TextView private lateinit var aosTimerText: TextView
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private var satPassList: MutableList<SatPass> = mutableListOf()
private var isTimerSet: Boolean = false private var isTimerSet: Boolean = false
private var satPassList: MutableList<SatPass> = mutableListOf()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val fragmentBinding = FragmentPassListBinding.bind(view) val binding = FragmentPassListBinding.bind(view)
mainActivity = activity as MainActivity 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) satPassAdapter = SatPassAdapter(viewModel)
aosTimerText = mainActivity.findViewById(R.id.toolbar_timer) aosTimerText = mainActivity.findViewById(R.id.toolbar_timer)
btnPassPrefs = mainActivity.findViewById(R.id.toolbar_filter) btnPassPrefs = mainActivity.findViewById(R.id.toolbar_filter)
setupComponents(fragmentBinding) setupObservers(binding)
setupObservers(fragmentBinding) setupComponents(binding)
if (viewModel.tleSelection.isNotEmpty() && viewModel.isFirstLaunch) { if (viewModel.isFirstLaunch) {
viewModel.getPasses() viewModel.calculatePasses()
viewModel.isFirstLaunch = false 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) { private fun setupObservers(binding: FragmentPassListBinding) {
viewModel.getSatPassList().observe(viewLifecycleOwner, Observer { viewModel.getSatPassList().observe(viewLifecycleOwner, Observer {
satPassList = it 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) { private fun showSatPassPrefsDialog(hoursAhead: Int, minEl: Double) {
val satPassPrefView = View.inflate(mainActivity, R.layout.dialog_pass_pref, null) val satPassPrefView = View.inflate(mainActivity, R.layout.dialog_pass_pref, null)
val etHoursAhead = satPassPrefView.findViewById<EditText>(R.id.pref_et_hoursAhead) val etHoursAhead = satPassPrefView.findViewById<EditText>(R.id.pref_et_hoursAhead)
@@ -142,7 +153,7 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
} }
else -> { else -> {
viewModel.setPassPrefs(hours, elevation) viewModel.setPassPrefs(hours, elevation)
viewModel.getPasses() viewModel.calculatePasses()
} }
} }
} else Toast.makeText( } else Toast.makeText(
@@ -160,31 +171,17 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
.show() .show()
} }
private fun showSelectSatDialog(tleMainList: List<TLE>, selection: MutableList<Int>) { private fun showSelectSatDialog(tleMainList: MutableList<SatEntry>) {
if (tleMainList.isEmpty()) { val listener = object : SatEntryDialogFragment.EntriesSubmitListener {
Toast.makeText(mainActivity, getString(R.string.err_update_tle), Toast.LENGTH_SHORT) override fun onEntriesSubmit(entries: MutableList<SatEntry>) {
.show() viewModel.updateEntriesSelection(entries)
} else {
val entriesList = mutableListOf<SatEntry>().apply {
tleMainList.withIndex().forEach {
this.add(SatEntry(it.index, it.value.name))
}
} }
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() { private fun setTimer() {
@@ -229,7 +226,7 @@ class PassListFragment : Fragment(R.layout.fragment_pass_list) {
val millisBeforeEnd = lastPass.pass.endTime.time.minus(timeNow.time) val millisBeforeEnd = lastPass.pass.endTime.time.minus(timeNow.time)
aosTimer = object : CountDownTimer(millisBeforeEnd, 1000) { aosTimer = object : CountDownTimer(millisBeforeEnd, 1000) {
override fun onFinish() { override fun onFinish() {
viewModel.getPasses() viewModel.calculatePasses()
} }
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.content.Context
import android.graphics.Canvas import android.graphics.Canvas
@@ -32,43 +32,65 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.amsacode.predict4java.SatPos 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.R
import com.rtbishop.look4sat.dagger.ViewModelFactory
import com.rtbishop.look4sat.data.SatPass
import com.rtbishop.look4sat.databinding.FragmentPolarViewBinding 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 com.rtbishop.look4sat.ui.adapters.TransmitterAdapter
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
import javax.inject.Inject
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class PolarViewFragment : Fragment(R.layout.fragment_polar_view) { class PolarViewFragment : Fragment(R.layout.fragment_polar_view) {
@Inject
lateinit var modelFactory: ViewModelFactory
private val args: PolarViewFragmentArgs by navArgs() private val args: PolarViewFragmentArgs by navArgs()
private lateinit var viewModel: SharedViewModel
private lateinit var viewModel: MainViewModel
private lateinit var satPass: SatPass private lateinit var satPass: SatPass
private lateinit var polarView: PolarView private lateinit var polarView: PolarView
private lateinit var transmitterAdapter: TransmitterAdapter
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private var transmitterAdapter: TransmitterAdapter = TransmitterAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val binding = FragmentPolarViewBinding.bind(view) val binding = FragmentPolarViewBinding.bind(view)
mainActivity = activity as MainActivity mainActivity = activity as MainActivity
viewModel = ViewModelProvider(mainActivity).get(MainViewModel::class.java) (mainActivity.application as Look4SatApp).appComponent.inject(this)
viewModel.getSatPassList().value?.let { viewModel = ViewModelProvider(mainActivity, modelFactory).get(SharedViewModel::class.java)
val refreshRate = viewModel.getRefreshRate() val refreshRate = viewModel.getRefreshRate()
satPass = it[args.satPassIndex]
mainActivity.supportActionBar?.title = satPass.tle.name
polarView = PolarView(mainActivity, refreshRate, binding) binding.recPolar.apply {
binding.framePolar.addView(polarView) layoutManager = LinearLayoutManager(mainActivity)
adapter = transmitterAdapter
setupTransRecycler(binding)
refreshView()
} }
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() { 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( inner class PolarView(
context: Context, context: Context,
private val updateFreq: Long, private val updateFreq: Long,
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@@ -26,14 +26,15 @@ import android.widget.Toast
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.rtbishop.look4sat.MainViewModel
import com.rtbishop.look4sat.R 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 { SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var viewModel: MainViewModel private lateinit var viewModel: SharedViewModel
private lateinit var keyLat: String private lateinit var keyLat: String
private lateinit var keyLon: String private lateinit var keyLon: String
private lateinit var keyAlt: String private lateinit var keyAlt: String
@@ -44,7 +45,7 @@ class SettingsFragment : PreferenceFragmentCompat(),
val provider = EditTextPreference.SimpleSummaryProvider.getInstance() val provider = EditTextPreference.SimpleSummaryProvider.getInstance()
mainActivity = activity as MainActivity 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) keyLat = mainActivity.getString(R.string.pref_lat_key)
keyLon = mainActivity.getString(R.string.pref_lon_key) keyLon = mainActivity.getString(R.string.pref_lon_key)
@@ -139,7 +140,7 @@ class SettingsFragment : PreferenceFragmentCompat(),
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { 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() { override fun onResume() {
@@ -17,7 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 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.app.Dialog
import android.os.Bundle import android.os.Bundle
@@ -26,18 +26,16 @@ import android.view.Window
import android.widget.SearchView import android.widget.SearchView
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.data.SatEntry
import com.rtbishop.look4sat.databinding.DialogSatEntryBinding import com.rtbishop.look4sat.databinding.DialogSatEntryBinding
import com.rtbishop.look4sat.repo.SatEntry
import com.rtbishop.look4sat.ui.adapters.SatEntryAdapter import com.rtbishop.look4sat.ui.adapters.SatEntryAdapter
import java.util.* 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 lateinit var entriesListener: EntriesSubmitListener
private val entryAdapter = SatEntryAdapter(entries)
private var entriesList = mutableListOf<SatEntry>()
private var selectionList = mutableListOf<Int>()
private var selectAllToggle = true private var selectAllToggle = true
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 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 { binding.dialogSearch.apply {
setOnQueryTextListener(this@SatEntryDialog) setOnQueryTextListener(this@SatEntryDialogFragment)
onActionViewExpanded() onActionViewExpanded()
clearFocus() clearFocus()
} }
binding.dialogRecycler.apply { binding.dialogRecycler.apply {
layoutManager = LinearLayoutManager(activity) layoutManager = LinearLayoutManager(activity)
adapter = satEntryAdapter adapter = entryAdapter
} }
binding.dialogBtnPositive.setOnClickListener { onPositiveClicked() } binding.dialogBtnPositive.setOnClickListener { onPositiveClicked() }
@@ -72,32 +67,27 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
return satEntryDialog return satEntryDialog
} }
fun setEntriesList(list: MutableList<SatEntry>): SatEntryDialog { fun setEntriesListener(listener: EntriesSubmitListener): SatEntryDialogFragment {
entriesList = list
return this
}
fun setSelectionList(list: MutableList<Int>): SatEntryDialog {
selectAllToggle = list.isEmpty()
selectionList = list
return this
}
fun setEntriesListener(listener: EntriesSubmitListener): SatEntryDialog {
entriesListener = listener entriesListener = listener
return this return this
} }
private fun checkSelectedEntries(entries: MutableList<SatEntry>, selection: MutableList<Int>) private fun filterEntries(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> {
: MutableList<SatEntry> { if (query.isEmpty()) return list
selection.forEach { entries[it].isSelected = true } return try {
return entries filterByCatNum(list, query.toInt())
} catch (e: NumberFormatException) {
filterByName(list, query)
}
} }
private fun filterEntries(list: MutableList<SatEntry>, query: String): MutableList<SatEntry> { private fun filterByCatNum(list: MutableList<SatEntry>, catNum: Int): MutableList<SatEntry> {
val searchQuery = query.toLowerCase(Locale.getDefault()) val filteredList = list.filter { it.catNum == catNum }
if ((searchQuery == "") or searchQuery.isEmpty()) return list 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>() val filteredList = mutableListOf<SatEntry>()
list.forEach { list.forEach {
val name = it.name.toLowerCase(Locale.getDefault()) val name = it.name.toLowerCase(Locale.getDefault())
@@ -107,10 +97,7 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
} }
private fun onPositiveClicked() { private fun onPositiveClicked() {
val listToSubmit = mutableListOf<Int>().apply { entriesListener.onEntriesSubmit(entries)
entriesList.forEach { if (it.isSelected) this.add(it.id) }
}
entriesListener.onEntriesSubmit(listToSubmit)
dismiss() dismiss()
} }
@@ -120,20 +107,19 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
private fun onNeutralClicked() { private fun onNeutralClicked() {
selectAllToggle = if (selectAllToggle) { selectAllToggle = if (selectAllToggle) {
entriesList.forEach { it.isSelected = true } entries.forEach { it.isSelected = true }
satEntryAdapter.setEntries(entriesList) entryAdapter.setEntries(entries)
false false
} else { } else {
entriesList.forEach { it.isSelected = false } entries.forEach { it.isSelected = false }
satEntryAdapter.setEntries(entriesList) entryAdapter.setEntries(entries)
true true
} }
} }
override fun onQueryTextChange(newText: String): Boolean { override fun onQueryTextChange(newText: String): Boolean {
entriesList = checkSelectedEntries(entriesList, selectionList) val filteredList = filterEntries(entries, newText)
val filteredList = filterEntries(entriesList, newText) entryAdapter.setEntries(filteredList)
satEntryAdapter.setEntries(filteredList)
return false return false
} }
@@ -142,6 +128,6 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener
} }
interface EntriesSubmitListener { 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 * You should have received a copy of the GNU General Public License
* along with this program; if not, visit http://www.fsf.org/ * 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 com.github.amsacode.predict4java.*
import java.util.* import java.util.*
@@ -253,14 +253,18 @@ class PassPredictor(private val tle: TLE, private val qth: GroundStationPosition
var polePassed = deadSpotNone var polePassed = deadSpotNone
val az1 = prevPos.azimuth / twoPi * 360.0 val az1 = prevPos.azimuth / twoPi * 360.0
val az2 = satPos.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 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 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 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 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 { private fun threeQuarterOrbitMinutes(): Int {
return (24.0 * 60.0 / tle.meanmo * 0.75).toInt() return (24.0 * 60.0 / tle.meanmo * 0.75).toInt()
} }
} }
@@ -20,35 +20,44 @@
package com.rtbishop.look4sat.utility package com.rtbishop.look4sat.utility
import android.content.SharedPreferences import android.content.SharedPreferences
import android.location.LocationManager
import android.util.Log
import androidx.core.content.edit import androidx.core.content.edit
import com.github.amsacode.predict4java.GroundStationPosition import com.github.amsacode.predict4java.GroundStationPosition
import javax.inject.Inject 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 keyHoursAhead = "hoursAhead"
private val keyMinElevation = "minEl" private val keyMinElevation = "minEl"
private val keyLatitude = "latitude" private val keyLatitude = "latitude"
private val keyLongitude = "longitude" private val keyLongitude = "longitude"
private val keyAltitude = "altitude" private val keyAltitude = "altitude"
private val keyRefreshRate = "rate" 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 { fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, 8) return preferences.getInt(keyHoursAhead, defaultHoursAhead)
} }
fun getMinElevation(): Double { fun getMinElevation(): Double {
return preferences.getDouble(keyMinElevation, 16.0) return preferences.getDouble(keyMinElevation, defaultMinEl)
} }
fun getRefreshRate(): Long { fun getRefreshRate(): Long {
return preferences.getString(keyRefreshRate, "3000")!!.toLong() return preferences.getString(keyRefreshRate, defaultRefreshRate)!!.toLong()
} }
fun getGroundStationPosition(): GroundStationPosition { fun getPosition(): GroundStationPosition {
val lat = preferences.getString(keyLatitude, "0.0")!!.toDouble() val lat = preferences.getString(keyLatitude, defaultGSP)!!.toDouble()
val lon = preferences.getString(keyLongitude, "0.0")!!.toDouble() val lon = preferences.getString(keyLongitude, defaultGSP)!!.toDouble()
val alt = preferences.getString(keyAltitude, "0.0")!!.toDouble() val alt = preferences.getString(keyAltitude, defaultGSP)!!.toDouble()
return GroundStationPosition(lat, lon, alt) 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 { preferences.edit {
putString(keyLatitude, gsp.latitude.toString()) putString(keyLatitude, gsp.latitude.toString())
putString(keyLongitude, gsp.longitude.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) { private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
putLong(key, double.toRawBits()) putLong(key, double.toRawBits())
} }
+1 -1
View File
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.AboutFragment"> tools:context=".ui.fragments.AboutFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.MapViewFragment"> tools:context=".ui.fragments.MapViewFragment">
<FrameLayout <FrameLayout
android:id="@+id/frameMap" android:id="@+id/frameMap"
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.PolarViewFragment"> tools:context=".ui.fragments.PolarViewFragment">
<FrameLayout <FrameLayout
android:id="@+id/frame_polar" android:id="@+id/frame_polar"
+5 -5
View File
@@ -26,7 +26,7 @@
<fragment <fragment
android:id="@+id/nav_pass_list" 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" android:label="@string/menu_pass_list"
tools:layout="@layout/fragment_pass_list"> tools:layout="@layout/fragment_pass_list">
<action <action
@@ -40,7 +40,7 @@
<fragment <fragment
android:id="@+id/nav_polar_view" 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" android:label="@string/menu_polar_view"
tools:layout="@layout/fragment_polar_view"> tools:layout="@layout/fragment_polar_view">
<argument <argument
@@ -50,18 +50,18 @@
<fragment <fragment
android:id="@+id/nav_map_view" 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" android:label="@string/menu_map_view"
tools:layout="@layout/fragment_map_view" /> tools:layout="@layout/fragment_map_view" />
<fragment <fragment
android:id="@+id/nav_settings" 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" /> android:label="@string/menu_settings" />
<fragment <fragment
android:id="@+id/nav_about" 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" android:label="@string/menu_about"
tools:layout="@layout/fragment_about" /> tools:layout="@layout/fragment_about" />
+6 -6
View File
@@ -26,9 +26,9 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.android.tools.build:gradle:3.6.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.1" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2"
} }
} }
@@ -44,15 +44,15 @@ task clean(type: Delete) {
} }
ext { ext {
material = '1.2.0-alpha05' material = '1.1.0'
appCompat = '1.1.0' appCompat = '1.1.0'
supportLib = '1.0.0' supportLib = '1.0.0'
constraintLayout = '1.1.3' constraintLayout = '1.1.3'
ktx = '1.2.0' ktx = '1.2.0'
preference = '1.1.0' preference = '1.1.1'
navigation = '2.2.1' navigation = '2.2.2'
lifecycle = '2.2.0' lifecycle = '2.2.0'
room = '2.2.5' room = '2.2.5'