Repositories refactoring and merge

This commit is contained in:
Arty Bishop committed 2021-05-16 19:13:10 +01:00
1 parent 1a59b5e3e1
commit 0110de8104
28 files changed
+309 -577

No files matched your search

@@ -23,7 +23,7 @@ import android.location.LocationManager
import androidx.room.Room import androidx.room.Room
import com.rtbishop.look4sat.data.* import com.rtbishop.look4sat.data.*
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.framework.DefaultLocationSource import com.rtbishop.look4sat.framework.PreferencesProvider
import com.rtbishop.look4sat.framework.api.NetworkDataSource import com.rtbishop.look4sat.framework.api.NetworkDataSource
import com.rtbishop.look4sat.framework.api.SatelliteService import com.rtbishop.look4sat.framework.api.SatelliteService
import com.rtbishop.look4sat.framework.db.RoomConverters import com.rtbishop.look4sat.framework.db.RoomConverters
@@ -52,20 +52,21 @@ object DataSourceModule {
@Provides @Provides
@Singleton @Singleton
fun provideLocationSource( fun providePreferenceSource(
qthConverter: QthConverter,
locationManager: LocationManager, locationManager: LocationManager,
preferences: SharedPreferences preferences: SharedPreferences
): LocationSource { ): PreferenceSource {
return DefaultLocationSource(locationManager, preferences) return PreferencesProvider(qthConverter, locationManager, preferences)
} }
@Provides @Provides
@Singleton @Singleton
fun provideLocationRepo( fun providePassesRepo(
locationSource: LocationSource, preferenceSource: PreferenceSource,
qthConverter: QthConverter @DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): LocationRepo { ): PassesRepo {
return LocationRepo(locationSource, qthConverter) return PassesRepo(preferenceSource, defaultDispatcher)
} }
@Provides @Provides
@@ -39,18 +39,18 @@ object InteractorsModule {
} }
@Provides @Provides
fun provideGetTransmittersForSatUseCase(satelliteRepo: SatelliteRepo): GetTransmittersForSat { fun provideGetSatTransmittersUseCase(satelliteRepo: SatelliteRepo): GetSatTransmitters {
return GetTransmittersForSat(satelliteRepo) return GetSatTransmitters(satelliteRepo)
} }
@Provides @Provides
fun provideImportDataFromStreamUseCase(satelliteRepo: SatelliteRepo): ImportDataFromStream { fun provideUpdateEntriesFromFileUseCase(satelliteRepo: SatelliteRepo): UpdateEntriesFromFile {
return ImportDataFromStream(satelliteRepo) return UpdateEntriesFromFile(satelliteRepo)
} }
@Provides @Provides
fun provideImportDataFromWebUseCase(satelliteRepo: SatelliteRepo): ImportDataFromWeb { fun provideUpdateEntriesFromWebUseCase(satelliteRepo: SatelliteRepo): UpdateEntriesFromWeb {
return ImportDataFromWeb(satelliteRepo) return UpdateEntriesFromWeb(satelliteRepo)
} }
@Provides @Provides
@@ -1,62 +0,0 @@
package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.core.content.edit
import com.rtbishop.look4sat.data.LocationSource
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.utility.round
import javax.inject.Inject
class DefaultLocationSource @Inject constructor(
private val locationManager: LocationManager,
private val preferences: SharedPreferences
) : LocationSource {
companion object {
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyAltitude = "stationAlt"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
override fun getLastKnownLocation(): StationPosition? {
return try {
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
if (location == null) null
else {
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1)
return StationPosition(latitude, longitude, altitude)
}
} catch (exception: SecurityException) {
null
}
}
override fun getMagDeclination(position: StationPosition): Float {
val lat = position.latitude.toFloat()
val lon = position.longitude.toFloat()
val alt = position.altitude.toFloat()
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
}
override fun loadStationPosition(): StationPosition {
val defaultSP = "0.0"
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
val altitude = preferences.getString(keyAltitude, null) ?: defaultSP
return StationPosition(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
}
override fun saveStationPosition(position: StationPosition) {
preferences.edit {
putString(keyLatitude, position.latitude.toString())
putString(keyLongitude, position.longitude.toString())
putString(keyAltitude, position.altitude.toString())
}
}
}
@@ -0,0 +1,155 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.core.content.edit
import com.rtbishop.look4sat.data.PreferenceSource
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.utility.round
import javax.inject.Inject
class PreferencesProvider @Inject constructor(
private val qthConverter: QthConverter,
private val locationManager: LocationManager,
private val preferences: SharedPreferences
) : PreferenceSource {
companion object {
const val keyModes = "satModes"
const val keyCompass = "compass"
const val keyTextLabels = "shouldUseTextLabels"
const val keyTimeUTC = "timeUTC"
const val keyHoursAhead = "hoursAhead"
const val keyMinElevation = "minElevation"
const val keyIsFirstLaunch = "shouldShowSplash"
const val keyRotator = "isRotatorEnabled"
const val keyRotatorAddress = "rotatorAddress"
const val keyRotatorPort = "rotatorPort"
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyAltitude = "stationAlt"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
override fun positionToQTH(lat: Double, lon: Double): String? {
return qthConverter.positionToQTH(lat, lon)
}
override fun loadStationPosition(): StationPosition {
val defaultSP = "0.0"
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
val altitude = preferences.getString(keyAltitude, null) ?: defaultSP
return StationPosition(latitude.toDouble(), longitude.toDouble(), altitude.toDouble())
}
override fun saveStationPosition(position: StationPosition) {
preferences.edit {
putString(keyLatitude, position.latitude.toString())
putString(keyLongitude, position.longitude.toString())
putString(keyAltitude, position.altitude.toString())
}
}
override fun updatePositionFromGPS(): Boolean {
return try {
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
if (location == null) false
else {
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
val altitude = location.altitude.round(1)
val stationPosition = StationPosition(latitude, longitude, altitude)
saveStationPosition(stationPosition)
return true
}
} catch (exception: SecurityException) {
false
}
}
override fun updatePositionFromQTH(qthString: String): Boolean {
val position = qthConverter.qthToPosition(qthString) ?: return false
val stationPosition = StationPosition(position.latitude, position.longitude, 0.0)
saveStationPosition(stationPosition)
return true
}
override fun getMagDeclination(): Float {
val stationPosition = loadStationPosition()
val lat = stationPosition.latitude.toFloat()
val lon = stationPosition.longitude.toFloat()
val alt = stationPosition.altitude.toFloat()
return GeomagneticField(lat, lon, alt, System.currentTimeMillis()).declination
}
override fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, 8)
}
override fun getMinElevation(): Double {
return preferences.getInt(keyMinElevation, 16).toDouble()
}
override fun shouldUseTextLabels(): Boolean {
return preferences.getBoolean(keyTextLabels, false)
}
override fun shouldUseUTC(): Boolean {
return preferences.getBoolean(keyTimeUTC, false)
}
override fun shouldUseCompass(): Boolean {
return preferences.getBoolean(keyCompass, true)
}
override fun isFirstLaunch(): Boolean {
return preferences.getBoolean(keyIsFirstLaunch, true)
}
override fun setFirstLaunchDone() {
preferences.edit { putBoolean(keyIsFirstLaunch, false) }
}
override fun saveModesSelection(modes: List<String>) {
val modesSet = modes.toSet()
preferences.edit {
putStringSet(keyModes, modesSet)
}
}
override fun loadModesSelection(): List<String> {
preferences.getStringSet(keyModes, setOf())?.let { modesSet ->
return modesSet.toList().sorted()
}
return emptyList()
}
override fun getRotatorServer(): Pair<String, Int>? {
return if (preferences.getBoolean(keyRotator, false)) {
val address = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
val port = preferences.getString(keyRotatorPort, null) ?: "4096"
Pair(address, port.toInt())
} else null
}
}
@@ -1,124 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.framework
import android.content.SharedPreferences
import androidx.core.content.edit
import com.rtbishop.look4sat.framework.model.TleSource
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PrefsManager @Inject constructor(private val preferences: SharedPreferences, moshi: Moshi) {
private val sourcesType = Types.newParameterizedType(List::class.java, TleSource::class.java)
private val sourcesAdapter = moshi.adapter<List<TleSource>>(sourcesType)
companion object {
const val keySources = "sourcesListJson"
const val keyModes = "satModes"
const val keyCompass = "compass"
const val keyTextLabels = "shouldUseTextLabels"
const val keyTimeUTC = "timeUTC"
const val keyHoursAhead = "hoursAhead"
const val keyMinElevation = "minElevation"
const val keyIsFirstLaunch = "shouldShowSplash"
const val keyRotator = "isRotatorEnabled"
const val keyRotatorAddress = "rotatorAddress"
const val keyRotatorPort = "rotatorPort"
}
fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, 8)
}
fun getMinElevation(): Double {
return preferences.getInt(keyMinElevation, 16).toDouble()
}
fun shouldUseTextLabels(): Boolean {
return preferences.getBoolean(keyTextLabels, false)
}
fun shouldUseUTC(): Boolean {
return preferences.getBoolean(keyTimeUTC, false)
}
fun shouldUseCompass(): Boolean {
return preferences.getBoolean(keyCompass, true)
}
fun isFirstLaunch(): Boolean {
return preferences.getBoolean(keyIsFirstLaunch, true)
}
fun setFirstLaunchDone() {
preferences.edit { putBoolean(keyIsFirstLaunch, false) }
}
fun loadTleSources(): List<TleSource> {
return try {
val sourcesString = preferences.getString(keySources, String())
if (sourcesString.isNullOrEmpty()) {
loadDefaultSources()
} else {
sourcesAdapter.fromJson(sourcesString) ?: loadDefaultSources()
}
} catch (exception: ClassCastException) {
loadDefaultSources()
}
}
fun saveTleSources(sources: List<TleSource>) {
val sourcesJson = sourcesAdapter.toJson(sources)
preferences.edit { putString(keySources, sourcesJson) }
}
fun loadDefaultSources(): List<TleSource> {
return listOf(
TleSource("https://celestrak.com/NORAD/elements/active.txt"),
TleSource("https://amsat.org/tle/current/nasabare.txt"),
TleSource("https://www.prismnet.com/~mmccants/tles/classfd.zip"),
TleSource("https://www.prismnet.com/~mmccants/tles/inttles.zip")
)
}
fun saveModesSelection(modes: List<String>) {
val modesSet = modes.toSet()
preferences.edit {
putStringSet(keyModes, modesSet)
}
}
fun loadModesSelection(): List<String> {
preferences.getStringSet(keyModes, setOf())?.let { modesSet ->
return modesSet.toList().sorted()
}
return emptyList()
}
fun getRotatorServer(): Pair<String, Int>? {
return if (preferences.getBoolean(keyRotator, false)) {
val address = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
val port = preferences.getString(keyRotatorPort, null) ?: "4096"
Pair(address, port.toInt())
} else null
}
}
@@ -29,7 +29,7 @@ class RoomDataSource(private val satelliteDao: SatelliteDao) : LocalDataSource {
satelliteDao.updateEntriesSelection(catNums, isSelected) satelliteDao.updateEntriesSelection(catNums, isSelected)
} }
override fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>> { override fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return satelliteDao.getSatTransmitters(catNum) return satelliteDao.getSatTransmitters(catNum)
.map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) } .map { satTransList -> DataMapper.satTransListToDomainTransList(satTransList) }
} }
@@ -1,20 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.framework.model
class TleSource(var url: String = String())
@@ -25,7 +25,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.ActivityMainBinding import com.rtbishop.look4sat.databinding.ActivityMainBinding
import com.rtbishop.look4sat.framework.PrefsManager import com.rtbishop.look4sat.framework.PreferencesProvider
import com.rtbishop.look4sat.utility.navigateSafe import com.rtbishop.look4sat.utility.navigateSafe
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
class Look4SatActivity : AppCompatActivity() { class Look4SatActivity : AppCompatActivity() {
@Inject @Inject
lateinit var prefsManager: PrefsManager lateinit var preferenceSource: PreferencesProvider
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -45,8 +45,8 @@ class Look4SatActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
val navHost = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment val navHost = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
binding.navBottom.setupWithNavController(navHost.navController) binding.navBottom.setupWithNavController(navHost.navController)
if (prefsManager.isFirstLaunch()) { if (preferenceSource.isFirstLaunch()) {
prefsManager.setFirstLaunchDone() preferenceSource.setFirstLaunchDone()
navHost.navController.navigateSafe(R.id.nav_dialog_splash) navHost.navController.navigateSafe(R.id.nav_dialog_splash)
} }
} }
@@ -22,18 +22,14 @@ import android.view.View
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.framework.model.Result
import com.rtbishop.look4sat.databinding.FragmentEntriesBinding import com.rtbishop.look4sat.databinding.FragmentEntriesBinding
import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.framework.model.TleSource import com.rtbishop.look4sat.framework.model.Result
import com.rtbishop.look4sat.utility.RecyclerDivider import com.rtbishop.look4sat.utility.RecyclerDivider
import com.rtbishop.look4sat.utility.getNavResult
import com.rtbishop.look4sat.utility.navigateSafe
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@@ -42,7 +38,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
private val viewModel: EntriesViewModel by viewModels() private val viewModel: EntriesViewModel by viewModels()
private val contentContract = ActivityResultContracts.GetContent() private val contentContract = ActivityResultContracts.GetContent()
private val filePicker = registerForActivityResult(contentContract) { uri -> private val filePicker = registerForActivityResult(contentContract) { uri ->
uri?.let { viewModel.importSatDataFromFile(uri) } uri?.let { viewModel.updateEntriesFromFile(uri) }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -62,9 +58,7 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
addItemDecoration(RecyclerDivider(R.drawable.rec_divider_light)) addItemDecoration(RecyclerDivider(R.drawable.rec_divider_light))
} }
importWeb.setOnClickListener { importWeb.setOnClickListener { viewModel.updateEntriesFromWeb(null) }
findNavController().navigateSafe(R.id.action_entries_to_sources)
}
importFile.setOnClickListener { filePicker.launch("*/*") } importFile.setOnClickListener { filePicker.launch("*/*") }
selectMode.setOnClickListener { viewModel.createModesDialog(requireContext()).show() } selectMode.setOnClickListener { viewModel.createModesDialog(requireContext()).show() }
selectAll.setOnClickListener { viewModel.selectCurrentItems() } selectAll.setOnClickListener { viewModel.selectCurrentItems() }
@@ -73,9 +67,6 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
viewModel.satData.observe(viewLifecycleOwner, { satData -> viewModel.satData.observe(viewLifecycleOwner, { satData ->
handleSatData(satData, binding, entriesAdapter) handleSatData(satData, binding, entriesAdapter)
}) })
getNavResult<List<String>>(R.id.nav_entries, "sources") { navResult ->
handleNavResult(navResult)
}
} }
private fun handleSatData( private fun handleSatData(
@@ -97,15 +88,11 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
is Result.Error -> { is Result.Error -> {
binding.entriesProgress.visibility = View.INVISIBLE binding.entriesProgress.visibility = View.INVISIBLE
binding.entriesRecycler.visibility = View.VISIBLE binding.entriesRecycler.visibility = View.VISIBLE
Snackbar.make(requireView(), R.string.entries_update_error, Snackbar.LENGTH_SHORT) val errorMsg = getString(R.string.entries_update_error)
.setAnchorView(R.id.nav_bottom).show() Snackbar.make(requireView(), errorMsg, Snackbar.LENGTH_SHORT).apply {
setAnchorView(R.id.nav_bottom)
}.show()
} }
} }
} }
private fun handleNavResult(result: List<String>) {
result.map { sourceUrl -> TleSource(sourceUrl) }.let { sources ->
viewModel.importSatDataFromSources(sources)
}
}
} }
@@ -26,32 +26,29 @@ import androidx.lifecycle.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.framework.PreferencesProvider
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.Result
import com.rtbishop.look4sat.framework.model.TleSource
import com.rtbishop.look4sat.interactors.GetSatItems import com.rtbishop.look4sat.interactors.GetSatItems
import com.rtbishop.look4sat.interactors.ImportDataFromStream import com.rtbishop.look4sat.interactors.UpdateEntriesFromFile
import com.rtbishop.look4sat.interactors.ImportDataFromWeb import com.rtbishop.look4sat.interactors.UpdateEntriesFromWeb
import com.rtbishop.look4sat.interactors.UpdateEntriesSelection import com.rtbishop.look4sat.interactors.UpdateEntriesSelection
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.system.measureTimeMillis
@HiltViewModel @HiltViewModel
class EntriesViewModel @Inject constructor( class EntriesViewModel @Inject constructor(
private val prefsManager: PrefsManager, private val preferenceSource: PreferencesProvider,
private val resolver: ContentResolver, private val resolver: ContentResolver,
private val getSatItems: GetSatItems, private val getSatItems: GetSatItems,
private val importDataFromStream: ImportDataFromStream, private val updateEntriesFromFile: UpdateEntriesFromFile,
private val importDataFromWeb: ImportDataFromWeb, private val updateEntriesFromWeb: UpdateEntriesFromWeb,
private val updateEntriesSelection: UpdateEntriesSelection, private val updateEntriesSelection: UpdateEntriesSelection,
) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener { ) : ViewModel(), EntriesAdapter.EntriesClickListener, SearchView.OnQueryTextListener {
private val transModes = MutableLiveData(prefsManager.loadModesSelection()) private val transModes = MutableLiveData(preferenceSource.loadModesSelection())
private val currentQuery = MutableLiveData(String()) private val currentQuery = MutableLiveData(String())
private val itemsWithModes = transModes.switchMap { modes -> private val itemsWithModes = transModes.switchMap { modes ->
liveData { getSatItems().collect { emit(filterByModes(it, modes)) } } liveData { getSatItems().collect { emit(filterByModes(it, modes)) } }
@@ -65,32 +62,22 @@ class EntriesViewModel @Inject constructor(
private var shouldSelectAll = true private var shouldSelectAll = true
val satData: LiveData<Result<List<SatItem>>> = _satData val satData: LiveData<Result<List<SatItem>>> = _satData
fun importSatDataFromFile(uri: Uri) { fun updateEntriesFromFile(uri: Uri) {
viewModelScope.launch { viewModelScope.launch {
_satData.value = Result.InProgress _satData.value = Result.InProgress
runCatching { runCatching {
resolver.openInputStream(uri)?.use { stream -> resolver.openInputStream(uri)?.use { stream -> updateEntriesFromFile(stream) }
importDataFromStream(stream)
}
}.onFailure { _satData.value = Result.Error(it) } }.onFailure { _satData.value = Result.Error(it) }
} }
} }
fun importSatDataFromSources(sources: List<TleSource>) { fun updateEntriesFromWeb(sources: List<String>?) {
viewModelScope.launch { viewModelScope.launch {
_satData.value = Result.InProgress _satData.value = Result.InProgress
val satSources = if (sources.isNotEmpty()) sources runCatching {
else prefsManager.loadDefaultSources() if (sources == null) updateEntriesFromWeb()
val updateMillis = measureTimeMillis { else updateEntriesFromWeb(sources)
try { }.onFailure { _satData.value = Result.Error(it) }
prefsManager.saveTleSources(satSources)
importDataFromWeb(satSources.map { it.url })
} catch (exception: Exception) {
_satData.value = Result.Error(exception)
Timber.d(exception)
}
}
Timber.d("Update from WEB took $updateMillis ms")
} }
} }
@@ -111,7 +98,7 @@ class EntriesViewModel @Inject constructor(
"PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT" "PSK", "PSK31", "PSK63", "QPSK", "QPSK31", "QPSK63", "SSTV", "USB", "WSJT"
) )
val savedModes = BooleanArray(modes.size) val savedModes = BooleanArray(modes.size)
val selectedModes = prefsManager.loadModesSelection().toMutableList() val selectedModes = preferenceSource.loadModesSelection().toMutableList()
selectedModes.forEach { savedModes[modes.indexOf(it)] = true } selectedModes.forEach { savedModes[modes.indexOf(it)] = true }
val dialogBuilder = MaterialAlertDialogBuilder(context).apply { val dialogBuilder = MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.modes_title)) setTitle(context.getString(R.string.modes_title))
@@ -123,7 +110,7 @@ class EntriesViewModel @Inject constructor(
} }
setPositiveButton(context.getString(android.R.string.ok)) { _, _ -> setPositiveButton(context.getString(android.R.string.ok)) { _, _ ->
transModes.value = selectedModes transModes.value = selectedModes
prefsManager.saveModesSelection(selectedModes) preferenceSource.saveModesSelection(selectedModes)
} }
setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ -> setNeutralButton(context.getString(android.R.string.cancel)) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
@@ -18,14 +18,13 @@
package com.rtbishop.look4sat.presentation.mapScreen package com.rtbishop.look4sat.presentation.mapScreen
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.data.LocationRepo import com.rtbishop.look4sat.data.PreferenceSource
import com.rtbishop.look4sat.di.DefaultDispatcher import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.domain.predict4kotlin.Position import com.rtbishop.look4sat.domain.predict4kotlin.Position
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
import com.rtbishop.look4sat.framework.model.SatData import com.rtbishop.look4sat.framework.model.SatData
import com.rtbishop.look4sat.interactors.GetSelectedSatellites import com.rtbishop.look4sat.interactors.GetSelectedSatellites
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
@@ -38,12 +37,11 @@ import kotlin.math.sqrt
@HiltViewModel @HiltViewModel
class MapViewModel @Inject constructor( class MapViewModel @Inject constructor(
private val getSelectedSatellites: GetSelectedSatellites, private val getSelectedSatellites: GetSelectedSatellites,
private val prefsManager: PrefsManager, private val preferenceSource: PreferenceSource,
private val locationRepo: LocationRepo,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) : ViewModel() { ) : ViewModel() {
private val gsp = locationRepo.getStationPosition() private val gsp = preferenceSource.loadStationPosition()
private var dataUpdateJob: Job? = null private var dataUpdateJob: Job? = null
private var allSatList = listOf<Satellite>() private var allSatList = listOf<Satellite>()
private lateinit var selectedSat: Satellite private lateinit var selectedSat: Satellite
@@ -78,7 +76,7 @@ class MapViewModel @Inject constructor(
} }
fun shouldUseTextLabels(): Boolean { fun shouldUseTextLabels(): Boolean {
return prefsManager.shouldUseTextLabels() return preferenceSource.shouldUseTextLabels()
} }
fun scrollSelection(decrement: Boolean) { fun scrollSelection(decrement: Boolean) {
@@ -172,7 +170,8 @@ class MapViewModel @Inject constructor(
val osmLat = getOsmLat(Math.toDegrees(satPos.latitude)) val osmLat = getOsmLat(Math.toDegrees(satPos.latitude))
val osmLon = getOsmLon(Math.toDegrees(satPos.longitude)) val osmLon = getOsmLon(Math.toDegrees(satPos.longitude))
val osmPos = Position(osmLat, osmLon) val osmPos = Position(osmLat, osmLon)
val qthLoc = locationRepo.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --" val qthLoc =
preferenceSource.positionToQTH(osmPos.latitude, osmPos.longitude) ?: "-- --"
val velocity = getOrbitalVelocity(satPos.altitude) val velocity = getOrbitalVelocity(satPos.altitude)
val satData = SatData( val satData = SatData(
sat, sat.tle.catnum, sat.tle.name, satPos.range, sat, sat.tle.catnum, sat.tle.name, satPos.range,
@@ -22,10 +22,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.rtbishop.look4sat.framework.model.Result import com.rtbishop.look4sat.framework.model.Result
import com.rtbishop.look4sat.utility.PassesRepo import com.rtbishop.look4sat.data.PassesRepo
import com.rtbishop.look4sat.interactors.GetSelectedSatellites import com.rtbishop.look4sat.interactors.GetSelectedSatellites
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.framework.PrefsManager import com.rtbishop.look4sat.framework.PreferencesProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@@ -35,7 +35,7 @@ import javax.inject.Inject
class PassesViewModel @Inject constructor( class PassesViewModel @Inject constructor(
private val getSelectedSatellites: GetSelectedSatellites, private val getSelectedSatellites: GetSelectedSatellites,
private val passesRepo: PassesRepo, private val passesRepo: PassesRepo,
private val prefsManager: PrefsManager private val preferenceSource: PreferencesProvider
) : ViewModel() { ) : ViewModel() {
private val _passes = MutableLiveData<Result<List<SatPass>>>(Result.InProgress) private val _passes = MutableLiveData<Result<List<SatPass>>>(Result.InProgress)
@@ -64,7 +64,7 @@ class PassesViewModel @Inject constructor(
} }
fun shouldUseUTC(): Boolean { fun shouldUseUTC(): Boolean {
return prefsManager.shouldUseUTC() return preferenceSource.shouldUseUTC()
} }
private suspend fun tickPasses(passes: List<SatPass>) = withContext(Dispatchers.Default) { private suspend fun tickPasses(passes: List<SatPass>) = withContext(Dispatchers.Default) {
@@ -18,14 +18,13 @@
package com.rtbishop.look4sat.presentation.polarScreen package com.rtbishop.look4sat.presentation.polarScreen
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.data.LocationRepo import com.rtbishop.look4sat.data.PreferenceSource
import com.rtbishop.look4sat.di.IoDispatcher import com.rtbishop.look4sat.di.IoDispatcher
import com.rtbishop.look4sat.domain.model.SatTrans import com.rtbishop.look4sat.domain.model.SatTrans
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.interactors.GetTransmittersForSat import com.rtbishop.look4sat.interactors.GetSatTransmitters
import com.rtbishop.look4sat.framework.OrientationProvider import com.rtbishop.look4sat.framework.OrientationProvider
import com.rtbishop.look4sat.utility.PassesRepo import com.rtbishop.look4sat.data.PassesRepo
import com.rtbishop.look4sat.framework.PrefsManager
import com.rtbishop.look4sat.utility.round import com.rtbishop.look4sat.utility.round
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
@@ -37,11 +36,10 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class PolarViewModel @Inject constructor( class PolarViewModel @Inject constructor(
private val locationRepo: LocationRepo,
private val orientationProvider: OrientationProvider, private val orientationProvider: OrientationProvider,
private val prefsManager: PrefsManager, private val preferenceSource: PreferenceSource,
private val passesRepo: PassesRepo, private val passesRepo: PassesRepo,
private val getTransmittersForSat: GetTransmittersForSat, private val getSatTransmitters: GetSatTransmitters,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher @IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : ViewModel(), OrientationProvider.OrientationListener { ) : ViewModel(), OrientationProvider.OrientationListener {
@@ -62,20 +60,20 @@ class PolarViewModel @Inject constructor(
} }
fun enableSensor() { fun enableSensor() {
if (prefsManager.shouldUseCompass()) orientationProvider.startListening(this) if (preferenceSource.shouldUseCompass()) orientationProvider.startListening(this)
} }
fun disableSensor() { fun disableSensor() {
if (prefsManager.shouldUseCompass()) orientationProvider.stopListening() if (preferenceSource.shouldUseCompass()) orientationProvider.stopListening()
} }
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) { override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
_orientation.value = Triple(azimuth + locationRepo.getMagDeclination(), pitch, roll) _orientation.value = Triple(azimuth + preferenceSource.getMagDeclination(), pitch, roll)
} }
private fun initRotatorControl(satPass: SatPass) { private fun initRotatorControl(satPass: SatPass) {
viewModelScope.launch { viewModelScope.launch {
val rotatorPrefs = prefsManager.getRotatorServer() val rotatorPrefs = preferenceSource.getRotatorServer()
if (rotatorPrefs != null) { if (rotatorPrefs != null) {
runCatching { runCatching {
withContext(ioDispatcher) { withContext(ioDispatcher) {
@@ -99,7 +97,7 @@ class PolarViewModel @Inject constructor(
private fun processTransmitters(satPass: SatPass) { private fun processTransmitters(satPass: SatPass) {
viewModelScope.launch { viewModelScope.launch {
getTransmittersForSat(satPass.catNum).collect { transList -> getSatTransmitters(satPass.catNum).collect { transList ->
while (isActive) { while (isActive) {
val timeNow = Date() val timeNow = Date()
val copiedList = transList.map { it.copy() } val copiedList = transList.map { it.copy() }
@@ -30,9 +30,8 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.LocationRepo import com.rtbishop.look4sat.data.PreferenceSource
import com.rtbishop.look4sat.framework.DefaultLocationSource import com.rtbishop.look4sat.framework.PreferencesProvider
import com.rtbishop.look4sat.framework.PrefsManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -40,10 +39,7 @@ import javax.inject.Inject
class PrefsFragment : PreferenceFragmentCompat() { class PrefsFragment : PreferenceFragmentCompat() {
@Inject @Inject
lateinit var locationRepo: LocationRepo lateinit var preferenceSource: PreferenceSource
@Inject
lateinit var prefsManager: PrefsManager
private val requestPermissionLauncher = private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
@@ -61,20 +57,20 @@ class PrefsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(DefaultLocationSource.keyPositionGPS)?.apply { findPreference<Preference>(PreferencesProvider.keyPositionGPS)?.apply {
setOnPreferenceClickListener { setOnPreferenceClickListener {
updatePositionFromGPS() updatePositionFromGPS()
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true
} }
} }
findPreference<Preference>(DefaultLocationSource.keyPositionQTH)?.apply { findPreference<Preference>(PreferencesProvider.keyPositionQTH)?.apply {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
updatePositionFromQth(newValue.toString()) updatePositionFromQth(newValue.toString())
} }
} }
findPreference<EditTextPreference>(PrefsManager.keyRotatorAddress)?.apply { findPreference<EditTextPreference>(PreferencesProvider.keyRotatorAddress)?.apply {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
if (Patterns.IP_ADDRESS.matcher(newValue.toString()).matches()) { if (Patterns.IP_ADDRESS.matcher(newValue.toString()).matches()) {
return@setOnPreferenceChangeListener true return@setOnPreferenceChangeListener true
@@ -85,7 +81,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
} }
} }
findPreference<EditTextPreference>(PrefsManager.keyRotatorPort)?.apply { findPreference<EditTextPreference>(PreferencesProvider.keyRotatorPort)?.apply {
setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER } setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER }
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val portValue = newValue.toString() val portValue = newValue.toString()
@@ -100,7 +96,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
} }
private fun updatePositionFromQth(qthString: String): Boolean { private fun updatePositionFromQth(qthString: String): Boolean {
return if (locationRepo.updatePositionFromQTH(qthString)) { return if (preferenceSource.updatePositionFromQTH(qthString)) {
showSnack(getString(R.string.pref_pos_success)) showSnack(getString(R.string.pref_pos_success))
true true
} else { } else {
@@ -113,7 +109,7 @@ class PrefsFragment : PreferenceFragmentCompat() {
val locPermString = Manifest.permission.ACCESS_FINE_LOCATION val locPermString = Manifest.permission.ACCESS_FINE_LOCATION
val locPermResult = ContextCompat.checkSelfPermission(requireContext(), locPermString) val locPermResult = ContextCompat.checkSelfPermission(requireContext(), locPermString)
if (locPermResult == PackageManager.PERMISSION_GRANTED) { if (locPermResult == PackageManager.PERMISSION_GRANTED) {
if (locationRepo.updatePositionFromGPS()) { if (preferenceSource.updatePositionFromGPS()) {
showSnack(getString(R.string.pref_pos_success)) showSnack(getString(R.string.pref_pos_success))
} else showSnack(getString(R.string.pref_pos_gps_null)) } else showSnack(getString(R.string.pref_pos_gps_null))
} else requestPermissionLauncher.launch(locPermString) } else requestPermissionLauncher.launch(locPermString)
@@ -1,70 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.presentation.sourcesDialog
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.RecyclerView
import com.rtbishop.look4sat.databinding.ItemTleSourceBinding
import com.rtbishop.look4sat.framework.model.TleSource
class SourcesAdapter(private val sources: MutableList<TleSource> = mutableListOf()) :
RecyclerView.Adapter<SourcesAdapter.TleSourceHolder>() {
fun getSources(): List<TleSource> {
return sources.filter { it.url.contains("https://") }
}
fun setSources(list: List<TleSource>) {
sources.clear()
sources.addAll(list)
}
fun addSource() {
sources.add(TleSource())
notifyItemInserted(itemCount - 1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TleSourceHolder {
val binding = ItemTleSourceBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return TleSourceHolder(binding)
}
override fun onBindViewHolder(holder: TleSourceHolder, position: Int) {
holder.bind(sources[position])
}
override fun getItemCount(): Int {
return sources.size
}
inner class TleSourceHolder(private val binding: ItemTleSourceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(source: TleSource) {
binding.tleSourceUrl.setText(source.url)
binding.tleSourceUrl.doOnTextChanged { text, _, _, _ -> source.url = text.toString() }
binding.tleSourceInputLayout.setEndIconOnClickListener {
sources.remove(source)
notifyItemRemoved(adapterPosition)
}
}
}
}
@@ -1,66 +0,0 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2021 Arty Bishop (bishop.arty@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.presentation.sourcesDialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.framework.PrefsManager
import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.utility.setNavResult
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class SourcesDialog : AppCompatDialogFragment() {
@Inject
lateinit var prefsManager: PrefsManager
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_sources, group, false)
}
override fun onViewCreated(view: View, state: Bundle?) {
super.onViewCreated(view, state)
val sourcesAdapter = SourcesAdapter().apply { setSources(prefsManager.loadTleSources()) }
DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT
)
sourcesRecycler.apply {
adapter = sourcesAdapter
layoutManager = LinearLayoutManager(requireContext())
}
sourceBtnAdd.setOnClickListener {
sourcesAdapter.addSource()
}
sourcesBtnPos.setOnClickListener {
setNavResult("sources", sourcesAdapter.getSources().map { it.url })
dismiss()
}
sourcesBtnNeg.setOnClickListener { dismiss() }
}
}
}
@@ -19,13 +19,9 @@ package com.rtbishop.look4sat.utility
import android.os.Bundle import android.os.Bundle
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.Navigator import androidx.navigation.Navigator
import androidx.navigation.fragment.findNavController
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
fun Long.toTimerString(): String { fun Long.toTimerString(): String {
@@ -53,26 +49,3 @@ fun NavController.navigateSafe(
navigate(resId, args, navOptions, navExtras) navigate(resId, args, navOptions, navExtras)
} }
} }
fun <T> Fragment.setNavResult(key: String, value: T) {
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, value)
}
fun <T> Fragment.getNavResult(@IdRes id: Int, key: String, onResult: (result: T) -> Unit) {
val navBackStackEntry = findNavController().getBackStackEntry(id)
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME
&& navBackStackEntry.savedStateHandle.contains(key)
) {
val result = navBackStackEntry.savedStateHandle.get<T>(key)
result?.let(onResult)
navBackStackEntry.savedStateHandle.remove<T>(key)
}
}
navBackStackEntry.lifecycle.addObserver(observer)
viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
navBackStackEntry.lifecycle.removeObserver(observer)
}
})
}
-11
View File
@@ -16,13 +16,6 @@
app:exitAnim="@anim/nav_default_exit_anim" app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" /> app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<action
android:id="@+id/action_entries_to_sources"
app:destination="@id/nav_dialog_sources"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment> </fragment>
<fragment <fragment
@@ -66,10 +59,6 @@
android:id="@+id/nav_info" android:id="@+id/nav_info"
android:name="com.rtbishop.look4sat.presentation.infoScreen.InfoFragment" android:name="com.rtbishop.look4sat.presentation.infoScreen.InfoFragment"
tools:layout="@layout/fragment_info" /> tools:layout="@layout/fragment_info" />
<dialog
android:id="@+id/nav_dialog_sources"
android:name="com.rtbishop.look4sat.presentation.sourcesDialog.SourcesDialog"
tools:layout="@layout/dialog_sources" />
<dialog <dialog
android:id="@+id/nav_dialog_splash" android:id="@+id/nav_dialog_splash"
android:name="com.rtbishop.look4sat.presentation.splashDialog.SplashDialog" android:name="com.rtbishop.look4sat.presentation.splashDialog.SplashDialog"
@@ -10,13 +10,13 @@ interface LocalDataSource {
fun getSatItems(): Flow<List<SatItem>> fun getSatItems(): Flow<List<SatItem>>
fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>>
suspend fun getSelectedSatellites(): List<Satellite> suspend fun getSelectedSatellites(): List<Satellite>
suspend fun updateEntries(entries: List<SatEntry>) suspend fun updateEntries(entries: List<SatEntry>)
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean)
fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>>
suspend fun updateTransmitters(satelliteTrans: List<SatTrans>) suspend fun updateTransmitters(satelliteTrans: List<SatTrans>)
} }
@@ -1,35 +0,0 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.predict4kotlin.QthConverter
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
class LocationRepo(
private val locationSource: LocationSource,
private val qthConverter: QthConverter
) {
fun getStationPosition(): StationPosition {
return locationSource.loadStationPosition()
}
fun getMagDeclination(): Float {
return locationSource.getMagDeclination(getStationPosition())
}
fun positionToQTH(lat: Double, lon: Double): String? {
return qthConverter.positionToQTH(lat, lon)
}
fun updatePositionFromGPS(): Boolean {
val stationPosition = locationSource.getLastKnownLocation() ?: return false
locationSource.saveStationPosition(stationPosition)
return true
}
fun updatePositionFromQTH(qthString: String): Boolean {
val position = qthConverter.qthToPosition(qthString) ?: return false
val stationPosition = StationPosition(position.latitude, position.longitude, 0.0)
locationSource.saveStationPosition(stationPosition)
return true
}
}
@@ -1,14 +0,0 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
interface LocationSource {
fun getLastKnownLocation(): StationPosition?
fun getMagDeclination(position: StationPosition): Float
fun loadStationPosition(): StationPosition
fun saveStationPosition(position: StationPosition)
}
@@ -15,26 +15,19 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.rtbishop.look4sat.utility package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.data.LocationRepo
import com.rtbishop.look4sat.di.DefaultDispatcher
import com.rtbishop.look4sat.domain.predict4kotlin.SatPass import com.rtbishop.look4sat.domain.predict4kotlin.SatPass
import com.rtbishop.look4sat.domain.predict4kotlin.Satellite import com.rtbishop.look4sat.domain.predict4kotlin.Satellite
import com.rtbishop.look4sat.framework.PrefsManager
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton class PassesRepo(
class PassesRepo @Inject constructor( private val preferenceSource: PreferenceSource,
private val locationRepo: LocationRepo, private val defaultDispatcher: CoroutineDispatcher
private val prefsManager: PrefsManager,
@DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher
) { ) {
private val _passes = MutableSharedFlow<List<SatPass>>(replay = 1) private val _passes = MutableSharedFlow<List<SatPass>>(replay = 1)
private var selectedSatellites = emptyList<Satellite>() private var selectedSatellites = emptyList<Satellite>()
@@ -64,15 +57,15 @@ class PassesRepo @Inject constructor(
} }
private fun getPasses(satellite: Satellite, refDate: Date): List<SatPass> { private fun getPasses(satellite: Satellite, refDate: Date): List<SatPass> {
val predictor = satellite.getPredictor(locationRepo.getStationPosition()) val predictor = satellite.getPredictor(preferenceSource.loadStationPosition())
return predictor.getPasses(refDate, prefsManager.getHoursAhead(), true) return predictor.getPasses(refDate, preferenceSource.getHoursAhead(), true)
} }
private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> { private fun filterPasses(passes: List<SatPass>, refDate: Date): List<SatPass> {
val timeFuture = Date(refDate.time + (prefsManager.getHoursAhead() * 3600 * 1000)) val timeFuture = Date(refDate.time + (preferenceSource.getHoursAhead() * 3600 * 1000))
return passes.filter { it.losDate.after(refDate) } return passes.filter { it.losDate.after(refDate) }
.filter { it.aosDate.before(timeFuture) } .filter { it.aosDate.before(timeFuture) }
.filter { it.maxElevation > prefsManager.getMinElevation() } .filter { it.maxElevation > preferenceSource.getMinElevation() }
.sortedBy { it.aosDate } .sortedBy { it.aosDate }
} }
} }
@@ -0,0 +1,38 @@
package com.rtbishop.look4sat.data
import com.rtbishop.look4sat.domain.predict4kotlin.StationPosition
interface PreferenceSource {
fun positionToQTH(lat: Double, lon: Double): String?
fun loadStationPosition(): StationPosition
fun saveStationPosition(position: StationPosition)
fun updatePositionFromGPS(): Boolean
fun updatePositionFromQTH(qthString: String): Boolean
fun getMagDeclination(): Float
fun getHoursAhead(): Int
fun getMinElevation(): Double
fun shouldUseTextLabels(): Boolean
fun shouldUseUTC(): Boolean
fun shouldUseCompass(): Boolean
fun isFirstLaunch(): Boolean
fun setFirstLaunchDone()
fun saveModesSelection(modes: List<String>)
fun loadModesSelection(): List<String>
fun getRotatorServer(): Pair<String, Int>?
}
@@ -22,24 +22,23 @@ class SatelliteRepo(
return localSource.getSatItems() return localSource.getSatItems()
} }
fun getTransmittersForSat(catNum: Int): Flow<List<SatTrans>> { fun getSatTransmitters(catNum: Int): Flow<List<SatTrans>> {
return localSource.getTransmittersForSat(catNum) return localSource.getSatTransmitters(catNum)
} }
suspend fun getSelectedSatellites(): List<Satellite> { suspend fun getSelectedSatellites(): List<Satellite> {
return localSource.getSelectedSatellites() return localSource.getSelectedSatellites()
} }
suspend fun importDataFromStream(stream: InputStream) = withContext(ioDispatcher) { suspend fun updateEntriesFromFile(stream: InputStream) = withContext(ioDispatcher) {
val importedEntries = Satellite.importElements(stream).map { tle -> SatEntry(tle) } localSource.updateEntries(importSatEntries(stream))
localSource.updateEntries(importedEntries)
} }
suspend fun importDataFromWeb(sources: List<String>) { suspend fun updateEntriesFromWeb(sources: List<String>) {
coroutineScope { coroutineScope {
launch(ioDispatcher) { launch(ioDispatcher) {
val importedEntries = mutableListOf<SatEntry>()
val streams = mutableListOf<InputStream>() val streams = mutableListOf<InputStream>()
val entries = mutableListOf<SatEntry>()
sources.forEach { source -> sources.forEach { source ->
remoteSource.fetchDataStream(source)?.let { stream -> remoteSource.fetchDataStream(source)?.let { stream ->
if (source.contains(".zip", true)) { if (source.contains(".zip", true)) {
@@ -50,11 +49,8 @@ class SatelliteRepo(
} }
} }
} }
streams.forEach { stream -> streams.forEach { stream -> entries.addAll(importSatEntries(stream)) }
val entries = Satellite.importElements(stream).map { tle -> SatEntry(tle) } localSource.updateEntries(entries)
importedEntries.addAll(entries)
}
localSource.updateEntries(importedEntries)
} }
launch(ioDispatcher) { launch(ioDispatcher) {
val transmitters = remoteSource.fetchTransmitters().filter { it.isAlive } val transmitters = remoteSource.fetchTransmitters().filter { it.isAlive }
@@ -66,4 +62,8 @@ class SatelliteRepo(
suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) { suspend fun updateEntriesSelection(catNums: List<Int>, isSelected: Boolean) {
localSource.updateEntriesSelection(catNums, isSelected) localSource.updateEntriesSelection(catNums, isSelected)
} }
private fun importSatEntries(stream: InputStream): List<SatEntry> {
return Satellite.importElements(stream).map { tle -> SatEntry(tle) }
}
} }
@@ -4,9 +4,9 @@ import com.rtbishop.look4sat.data.SatelliteRepo
import com.rtbishop.look4sat.domain.model.SatTrans import com.rtbishop.look4sat.domain.model.SatTrans
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class GetTransmittersForSat(private val satelliteRepo: SatelliteRepo) { class GetSatTransmitters(private val satelliteRepo: SatelliteRepo) {
operator fun invoke(catNum: Int): Flow<List<SatTrans>> { operator fun invoke(catNum: Int): Flow<List<SatTrans>> {
return satelliteRepo.getTransmittersForSat(catNum) return satelliteRepo.getSatTransmitters(catNum)
} }
} }
@@ -1,10 +0,0 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.data.SatelliteRepo
class ImportDataFromWeb(private val satelliteRepo: SatelliteRepo) {
suspend operator fun invoke(sources: List<String>) {
satelliteRepo.importDataFromWeb(sources)
}
}
@@ -3,9 +3,9 @@ package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.data.SatelliteRepo import com.rtbishop.look4sat.data.SatelliteRepo
import java.io.InputStream import java.io.InputStream
class ImportDataFromStream(private val satelliteRepo: SatelliteRepo) { class UpdateEntriesFromFile(private val satelliteRepo: SatelliteRepo) {
suspend operator fun invoke(stream: InputStream) { suspend operator fun invoke(stream: InputStream) {
satelliteRepo.importDataFromStream(stream) satelliteRepo.updateEntriesFromFile(stream)
} }
} }
@@ -0,0 +1,17 @@
package com.rtbishop.look4sat.interactors
import com.rtbishop.look4sat.data.SatelliteRepo
class UpdateEntriesFromWeb(private val satelliteRepo: SatelliteRepo) {
private val defaultSources = listOf(
"https://celestrak.com/NORAD/elements/active.txt",
"https://amsat.org/tle/current/nasabare.txt",
"https://www.prismnet.com/~mmccants/tles/classfd.zip",
"https://www.prismnet.com/~mmccants/tles/inttles.zip"
)
suspend operator fun invoke(sources: List<String> = defaultSources) {
satelliteRepo.updateEntriesFromWeb(sources)
}
}