diff --git a/app/build.gradle b/app/build.gradle index 6715e645..b92c851f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,6 +72,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle" // room implementation "androidx.room:room-runtime:$room" diff --git a/app/src/main/java/com/rtbishop/look4sat/Look4SatApp.kt b/app/src/main/java/com/rtbishop/look4sat/Look4SatApp.kt index 20c73a74..a2708ea5 100644 --- a/app/src/main/java/com/rtbishop/look4sat/Look4SatApp.kt +++ b/app/src/main/java/com/rtbishop/look4sat/Look4SatApp.kt @@ -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() { diff --git a/app/src/main/java/com/rtbishop/look4sat/MainViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/MainViewModel.kt deleted file mode 100644 index 0970f0de..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/MainViewModel.kt +++ /dev/null @@ -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>() - private val _debugMessage = MutableLiveData() - private val _gsp = MutableLiveData() - private val _isListRefreshing = MutableLiveData() - 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 = _gsp - fun getDebugMessage(): LiveData = _debugMessage - fun getSatPassList(): LiveData> = _satPassList - fun getRefreshing(): LiveData = _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) { - tleSelection = list - dataManager.saveSelectionList(list) - } - - fun getPasses() { - _isListRefreshing.value = true - calculationJob?.cancel() - var passList = mutableListOf() - 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, - hoursAhead: Int - ): MutableList { - 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 { - return repository.getTransmittersForSat(id) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/di/AppComponent.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/AppComponent.kt similarity index 63% rename from app/src/main/java/com/rtbishop/look4sat/di/AppComponent.kt rename to app/src/main/java/com/rtbishop/look4sat/dagger/AppComponent.kt index 9bf0567a..3d798bd2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/AppComponent.kt +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/AppComponent.kt @@ -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) } diff --git a/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelFactory.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelFactory.kt new file mode 100644 index 00000000..cdc135ac --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelFactory.kt @@ -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, + @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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) + } + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelKey.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelKey.kt new file mode 100644 index 00000000..dadc95f9 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/ViewModelKey.kt @@ -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) diff --git a/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/NetworkModule.kt similarity index 82% rename from app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt rename to app/src/main/java/com/rtbishop/look4sat/dagger/modules/NetworkModule.kt index 462bdb61..06052ea0 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/NetworkModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/NetworkModule.kt @@ -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() - } } diff --git a/app/src/main/java/com/rtbishop/look4sat/di/StorageModule.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/PersistenceModule.kt similarity index 55% rename from app/src/main/java/com/rtbishop/look4sat/di/StorageModule.kt rename to app/src/main/java/com/rtbishop/look4sat/dagger/modules/PersistenceModule.kt index fda1ed3c..2438fd07 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/StorageModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/PersistenceModule.kt @@ -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() + } } diff --git a/app/src/main/java/com/rtbishop/look4sat/dagger/modules/RepoModule.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/RepoModule.kt new file mode 100644 index 00000000..efffb9aa --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/RepoModule.kt @@ -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) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/di/AndroidModule.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/UtilityModule.kt similarity index 80% rename from app/src/main/java/com/rtbishop/look4sat/di/AndroidModule.kt rename to app/src/main/java/com/rtbishop/look4sat/dagger/modules/UtilityModule.kt index 27b1f029..0f8de269 100644 --- a/app/src/main/java/com/rtbishop/look4sat/di/AndroidModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/UtilityModule.kt @@ -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) + } } diff --git a/app/src/main/java/com/rtbishop/look4sat/dagger/modules/ViewModelModule.kt b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/ViewModelModule.kt new file mode 100644 index 00000000..476ce8d2 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/dagger/modules/ViewModelModule.kt @@ -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 +} diff --git a/app/src/main/java/com/rtbishop/look4sat/data/SatEntry.kt b/app/src/main/java/com/rtbishop/look4sat/data/SatEntry.kt new file mode 100644 index 00000000..99d3f12e --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/data/SatEntry.kt @@ -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 +} diff --git a/app/src/main/java/com/rtbishop/look4sat/repo/SatPass.kt b/app/src/main/java/com/rtbishop/look4sat/data/SatPass.kt similarity index 92% rename from app/src/main/java/com/rtbishop/look4sat/repo/SatPass.kt rename to app/src/main/java/com/rtbishop/look4sat/data/SatPass.kt index cd18905e..e6fbf89c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repo/SatPass.kt +++ b/app/src/main/java/com/rtbishop/look4sat/data/SatPass.kt @@ -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, diff --git a/app/src/main/java/com/rtbishop/look4sat/repo/Transmitter.kt b/app/src/main/java/com/rtbishop/look4sat/data/Transmitter.kt similarity index 94% rename from app/src/main/java/com/rtbishop/look4sat/repo/Transmitter.kt rename to app/src/main/java/com/rtbishop/look4sat/data/Transmitter.kt index 9a4d288c..a07c0088 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repo/Transmitter.kt +++ b/app/src/main/java/com/rtbishop/look4sat/data/Transmitter.kt @@ -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 ) diff --git a/app/src/main/java/com/rtbishop/look4sat/di/UtilityModule.kt b/app/src/main/java/com/rtbishop/look4sat/di/UtilityModule.kt deleted file mode 100644 index afbfd55c..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/di/UtilityModule.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/network/RemoteDataSource.kt b/app/src/main/java/com/rtbishop/look4sat/network/RemoteDataSource.kt index 3a93bc95..259548c4 100644 --- a/app/src/main/java/com/rtbishop/look4sat/network/RemoteDataSource.kt +++ b/app/src/main/java/com/rtbishop/look4sat/network/RemoteDataSource.kt @@ -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): InputStream { +) : RemoteSource { + + override suspend fun fetchTleStream(urlList: List): InputStream { val streamTable = Hashtable() - 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 { - return api.fetchTransmittersList() + override suspend fun fetchTransmitters(): List { + return api.fetchTransmitters() } } diff --git a/app/src/main/java/com/rtbishop/look4sat/network/RemoteSource.kt b/app/src/main/java/com/rtbishop/look4sat/network/RemoteSource.kt new file mode 100644 index 00000000..8936b193 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/network/RemoteSource.kt @@ -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): InputStream + + suspend fun fetchTransmitters(): List +} diff --git a/app/src/main/java/com/rtbishop/look4sat/network/TransmittersApi.kt b/app/src/main/java/com/rtbishop/look4sat/network/TransmittersApi.kt index d4749fd2..a0abc5b6 100644 --- a/app/src/main/java/com/rtbishop/look4sat/network/TransmittersApi.kt +++ b/app/src/main/java/com/rtbishop/look4sat/network/TransmittersApi.kt @@ -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 + suspend fun fetchTransmitters(): List } diff --git a/app/src/main/java/com/rtbishop/look4sat/persistence/LocalDataSource.kt b/app/src/main/java/com/rtbishop/look4sat/persistence/LocalDataSource.kt new file mode 100644 index 00000000..2752f6ba --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/persistence/LocalDataSource.kt @@ -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) { + entriesDao.insertEntries(entries) + } + + override suspend fun getAllEntries(): List { + return entriesDao.getAllEntries() + } + + override suspend fun getSelectedEntries(): List { + return entriesDao.getSelectedEntries() + } + + override suspend fun updateEntriesSelection(entries: List) { + entriesDao.updateEntriesSelection(entries) + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + + override suspend fun insertTransmitters(transmitters: List) { + transmittersDao.insertTransmitters(transmitters) + } + + override suspend fun getTransmittersByCatNum(catNum: Int): List { + return transmittersDao.getTransmittersByCatNum(catNum) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/persistence/LocalSource.kt b/app/src/main/java/com/rtbishop/look4sat/persistence/LocalSource.kt new file mode 100644 index 00000000..f87a190e --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/persistence/LocalSource.kt @@ -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) + + suspend fun getAllEntries(): List + + suspend fun getSelectedEntries(): List + + suspend fun updateEntriesSelection(entries: List) + + //////////////////////////////////////////////////////////////////////////////////////////////// + + suspend fun insertTransmitters(transmitters: List) + + suspend fun getTransmittersByCatNum(catNum: Int): List +} diff --git a/app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDb.kt b/app/src/main/java/com/rtbishop/look4sat/persistence/SatelliteDb.kt similarity index 64% rename from app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDb.kt rename to app/src/main/java/com/rtbishop/look4sat/persistence/SatelliteDb.kt index 8ac0c0c8..812def65 100644 --- a/app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDb.kt +++ b/app/src/main/java/com/rtbishop/look4sat/persistence/SatelliteDb.kt @@ -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 } diff --git a/app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDao.kt b/app/src/main/java/com/rtbishop/look4sat/persistence/dao/EntriesDao.kt similarity index 65% rename from app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDao.kt rename to app/src/main/java/com/rtbishop/look4sat/persistence/dao/EntriesDao.kt index 74465b6f..b660dc81 100644 --- a/app/src/main/java/com/rtbishop/look4sat/storage/TransmittersDao.kt +++ b/app/src/main/java/com/rtbishop/look4sat/persistence/dao/EntriesDao.kt @@ -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) + suspend fun insertEntries(entries: List) - @Query("SELECT * FROM transmitters WHERE isAlive = 1 and noradCatId = :id") - suspend fun getTransmittersForSat(id: Int): List + @Query("SELECT * FROM entries ORDER BY name ASC") + suspend fun getAllEntries(): List + + @Query("SELECT * FROM entries WHERE isSelected = 1 ORDER BY name ASC") + suspend fun getSelectedEntries(): List + + @Update + suspend fun updateEntriesSelection(entries: List) } diff --git a/app/src/main/java/com/rtbishop/look4sat/persistence/dao/TransmittersDao.kt b/app/src/main/java/com/rtbishop/look4sat/persistence/dao/TransmittersDao.kt new file mode 100644 index 00000000..2186fa34 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/persistence/dao/TransmittersDao.kt @@ -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) + + @Query("SELECT * FROM transmitters WHERE isAlive = 1 and catNum = :catNum") + suspend fun getTransmittersByCatNum(catNum: Int): List +} diff --git a/app/src/main/java/com/rtbishop/look4sat/repo/DefaultRepository.kt b/app/src/main/java/com/rtbishop/look4sat/repo/DefaultRepository.kt new file mode 100644 index 00000000..1d31e415 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/repo/DefaultRepository.kt @@ -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) { + 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 { + return localSource.getAllEntries() + } + + override suspend fun getSelectedEntries(): List { + return localSource.getSelectedEntries() + } + + override suspend fun updateEntriesSelection(entries: List) { + 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 { + return localSource.getTransmittersByCatNum(catNum) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/repo/Repository.kt b/app/src/main/java/com/rtbishop/look4sat/repo/Repository.kt index 78552067..b9e7a01c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/repo/Repository.kt +++ b/app/src/main/java/com/rtbishop/look4sat/repo/Repository.kt @@ -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): InputStream { - return remoteSource.getStreamForUrl(urlList) - } +interface Repository { - suspend fun updateTransmittersDatabase() { - localSource.insertTransmitters(remoteSource.fetchTransmittersList()) - } + suspend fun updateEntriesFrom(urlList: List) - suspend fun getTransmittersForSat(id: Int): List { - return localSource.getTransmittersForSat(id) - } + suspend fun getAllEntries(): List + + suspend fun getSelectedEntries(): List + + suspend fun updateEntriesSelection(entries: List) + + //////////////////////////////////////////////////////////////////////////////////////////////// + + suspend fun updateTransmitters() + + suspend fun getTransmittersByCatNum(catNum: Int): List } diff --git a/app/src/main/java/com/rtbishop/look4sat/repo/SatEntry.kt b/app/src/main/java/com/rtbishop/look4sat/repo/SatEntry.kt deleted file mode 100755 index 921cf030..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/repo/SatEntry.kt +++ /dev/null @@ -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) diff --git a/app/src/main/java/com/rtbishop/look4sat/storage/LocalDataSource.kt b/app/src/main/java/com/rtbishop/look4sat/storage/LocalDataSource.kt deleted file mode 100644 index 862d622c..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/storage/LocalDataSource.kt +++ /dev/null @@ -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) { - transmittersDao.insertTransmitters(transmitters) - } - - suspend fun getTransmittersForSat(id: Int): List { - return transmittersDao.getTransmittersForSat(id) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/MainActivity.kt b/app/src/main/java/com/rtbishop/look4sat/ui/MainActivity.kt index f81001a9..16676f80 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/MainActivity.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/MainActivity.kt @@ -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() } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt new file mode 100644 index 00000000..202a7499 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/ui/SharedViewModel.kt @@ -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>() + fun getSatPassList(): LiveData> = _satPassList + + private val _gsp = MutableLiveData(prefsManager.getPosition()) + fun getGSP(): LiveData = _gsp + + private val _isListRefreshing = MutableLiveData() + fun getRefreshing(): LiveData = _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) { + 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 { + return repository.getAllEntries() + } + + fun calculatePasses() { + _isListRefreshing.postValue(true) + calculationJob?.cancel() + var passList = mutableListOf() + 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 { + val passList = mutableListOf() + 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, + dateNow: Date, + hoursAhead: Int + ): MutableList { + 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 + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatEntryAdapter.kt b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatEntryAdapter.kt index ded8edf7..6842c192 100755 --- a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatEntryAdapter.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatEntryAdapter.kt @@ -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) : +class SatEntryAdapter(private var entries: MutableList) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SatEntryHolder { @@ -36,25 +36,27 @@ internal class SatEntryAdapter(private var entriesList: MutableList) : } 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) { - entriesList = entries + fun setEntries(list: MutableList) { + 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 + } } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatPassAdapter.kt b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatPassAdapter.kt index b8d4d5f3..27c73824 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatPassAdapter.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/SatPassAdapter.kt @@ -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() { private var satPassList = mutableListOf() diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/TransmitterAdapter.kt b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/TransmitterAdapter.kt index 3af59189..b9091688 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/adapters/TransmitterAdapter.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/adapters/TransmitterAdapter.kt @@ -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() { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/AboutFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/AboutFragment.kt similarity index 97% rename from app/src/main/java/com/rtbishop/look4sat/ui/AboutFragment.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/AboutFragment.kt index 43c24b5a..d8e0cad2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/AboutFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/AboutFragment.kt @@ -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 diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/MapViewFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt similarity index 94% rename from app/src/main/java/com/rtbishop/look4sat/ui/MapViewFragment.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt index 83900513..7b5890a3 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/MapViewFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/MapViewFragment.kt @@ -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() diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/PassListFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PassListFragment.kt similarity index 82% rename from app/src/main/java/com/rtbishop/look4sat/ui/PassListFragment.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/PassListFragment.kt index a9dfa3ea..9f8948a7 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/PassListFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PassListFragment.kt @@ -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 = mutableListOf() private var isTimerSet: Boolean = false + private var satPassList: MutableList = 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(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, selection: MutableList) { - if (tleMainList.isEmpty()) { - Toast.makeText(mainActivity, getString(R.string.err_update_tle), Toast.LENGTH_SHORT) - .show() - } else { - val entriesList = mutableListOf().apply { - tleMainList.withIndex().forEach { - this.add(SatEntry(it.index, it.value.name)) - } + private fun showSelectSatDialog(tleMainList: MutableList) { + val listener = object : SatEntryDialogFragment.EntriesSubmitListener { + override fun onEntriesSubmit(entries: MutableList) { + viewModel.updateEntriesSelection(entries) } - - val listener = object : SatEntryDialog.EntriesSubmitListener { - override fun onEntriesSubmit(list: MutableList) { - 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) { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/PolarViewFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt similarity index 81% rename from app/src/main/java/com/rtbishop/look4sat/ui/PolarViewFragment.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt index f60f0508..0d73565f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/PolarViewFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PolarViewFragment.kt @@ -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, diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/SettingsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PreferencesFragment.kt similarity index 94% rename from app/src/main/java/com/rtbishop/look4sat/ui/SettingsFragment.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/PreferencesFragment.kt index 55fcfea0..18d1a48b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/ui/SettingsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/PreferencesFragment.kt @@ -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() { diff --git a/app/src/main/java/com/rtbishop/look4sat/ui/SatEntryDialog.kt b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/SatEntryDialogFragment.kt similarity index 65% rename from app/src/main/java/com/rtbishop/look4sat/ui/SatEntryDialog.kt rename to app/src/main/java/com/rtbishop/look4sat/ui/fragments/SatEntryDialogFragment.kt index cbecb610..b6e214c8 100755 --- a/app/src/main/java/com/rtbishop/look4sat/ui/SatEntryDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/ui/fragments/SatEntryDialogFragment.kt @@ -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) : + AppCompatDialogFragment(), SearchView.OnQueryTextListener { - private lateinit var satEntryAdapter: SatEntryAdapter private lateinit var entriesListener: EntriesSubmitListener - - private var entriesList = mutableListOf() - private var selectionList = mutableListOf() + 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): SatEntryDialog { - entriesList = list - return this - } - - fun setSelectionList(list: MutableList): 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, selection: MutableList) - : MutableList { - selection.forEach { entries[it].isSelected = true } - return entries + private fun filterEntries(list: MutableList, query: String): MutableList { + if (query.isEmpty()) return list + return try { + filterByCatNum(list, query.toInt()) + } catch (e: NumberFormatException) { + filterByName(list, query) + } } - private fun filterEntries(list: MutableList, query: String): MutableList { - val searchQuery = query.toLowerCase(Locale.getDefault()) - if ((searchQuery == "") or searchQuery.isEmpty()) return list + private fun filterByCatNum(list: MutableList, catNum: Int): MutableList { + val filteredList = list.filter { it.catNum == catNum } + return filteredList as MutableList + } + private fun filterByName(list: MutableList, query: String): MutableList { + val searchQuery = query.toLowerCase(Locale.getDefault()) val filteredList = mutableListOf() list.forEach { val name = it.name.toLowerCase(Locale.getDefault()) @@ -107,10 +97,7 @@ class SatEntryDialog : AppCompatDialogFragment(), SearchView.OnQueryTextListener } private fun onPositiveClicked() { - val listToSubmit = mutableListOf().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) + fun onEntriesSubmit(entries: MutableList) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/utility/Converters.kt b/app/src/main/java/com/rtbishop/look4sat/utility/Converters.kt new file mode 100644 index 00000000..b61cb63c --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/utility/Converters.kt @@ -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) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/utility/DataManager.kt b/app/src/main/java/com/rtbishop/look4sat/utility/DataManager.kt deleted file mode 100644 index 1e964d10..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/utility/DataManager.kt +++ /dev/null @@ -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) { - 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) { - 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 { - return try { - val tleStream = context.openFileInput(tleMainListFileName) - val tleList = ObjectInputStream(tleStream).readObject() - tleList as List - } 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 { - return try { - val selectionStream = context.openFileInput(tleSelectionFileName) - val selectionList = ObjectInputStream(selectionStream).readObject() - selectionList as MutableList - } catch (exception: FileNotFoundException) { - Log.w(tag, context.getString(R.string.err_no_selection_file)) - mutableListOf() - } catch (exception: IOException) { - Log.w(tag, exception.toString()) - mutableListOf() - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/predict4kotlin/PassPredictor.kt b/app/src/main/java/com/rtbishop/look4sat/utility/PassPredictor.kt similarity index 96% rename from app/src/main/java/com/rtbishop/look4sat/predict4kotlin/PassPredictor.kt rename to app/src/main/java/com/rtbishop/look4sat/utility/PassPredictor.kt index a16c9d9e..fde89fe5 100644 --- a/app/src/main/java/com/rtbishop/look4sat/predict4kotlin/PassPredictor.kt +++ b/app/src/main/java/com/rtbishop/look4sat/utility/PassPredictor.kt @@ -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() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/rtbishop/look4sat/utility/PrefsManager.kt b/app/src/main/java/com/rtbishop/look4sat/utility/PrefsManager.kt index a93908cb..c43d2e6f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/utility/PrefsManager.kt +++ b/app/src/main/java/com/rtbishop/look4sat/utility/PrefsManager.kt @@ -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()) } diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 7ce09874..3100c2b5 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -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"> + tools:context=".ui.fragments.MapViewFragment"> + tools:context=".ui.fragments.PolarViewFragment"> diff --git a/build.gradle b/build.gradle index a315222e..c1a08671 100644 --- a/build.gradle +++ b/build.gradle @@ -26,9 +26,9 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71" - classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.1" + classpath 'com.android.tools.build:gradle:3.6.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2" } } @@ -44,15 +44,15 @@ task clean(type: Delete) { } ext { - material = '1.2.0-alpha05' + material = '1.1.0' appCompat = '1.1.0' supportLib = '1.0.0' constraintLayout = '1.1.3' ktx = '1.2.0' - preference = '1.1.0' + preference = '1.1.1' - navigation = '2.2.1' + navigation = '2.2.2' lifecycle = '2.2.0' room = '2.2.5'