Switched to a generic LocationHandler.kt

This commit is contained in:
Arty Bishop committed 2022-01-09 18:03:21 +00:00
1 parent eebe691419
commit a9a4aa9737
24 files changed
+462 -557

No files matched your search

@@ -0,0 +1,108 @@
/*
* 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.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.LocationHandler
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.round
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class LocationProvider @Inject constructor(
@ApplicationContext private val context: Context,
private val settingsProvider: SettingsProvider,
) : LocationListener, LocationHandler {
private val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val providerDef = LocationManager.PASSIVE_PROVIDER
private val providerNet = LocationManager.NETWORK_PROVIDER
private val providerGps = LocationManager.GPS_PROVIDER
private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
private val _stationPosition = MutableSharedFlow<DataState<GeoPos>>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
private var currentPosition = settingsProvider.loadStationPosition()
override val stationPosition: SharedFlow<DataState<GeoPos>> = _stationPosition
override fun getStationPosition(): GeoPos = currentPosition
override fun setStationPosition(latitude: Double, longitude: Double) {
if (QthConverter.isValidPosition(latitude, longitude)) {
val newLat = latitude.round(4)
val newLon = longitude.round(4)
currentPosition = GeoPos(newLat, newLon)
settingsProvider.saveStationPosition(newLat, newLon)
_stationPosition.tryEmit(DataState.Success(currentPosition))
} else _stationPosition.tryEmit(DataState.Error(context.getString(R.string.pref_pos_gps_null)))
}
override fun setPositionFromLocation() {
val result = ContextCompat.checkSelfPermission(context, locationFine)
if (manager.isProviderEnabled(providerDef) && result == PackageManager.PERMISSION_GRANTED) {
val location = manager.getLastKnownLocation(providerDef)
if (location != null) {
setStationPosition(location.latitude, location.longitude)
} else setPositionFromGps()
} else _stationPosition.tryEmit(DataState.Error(context.getString(R.string.pref_pos_gps_null)))
}
override fun setPositionFromNet() {
val result = ContextCompat.checkSelfPermission(context, locationCoarse)
if (manager.isProviderEnabled(providerNet) && result == PackageManager.PERMISSION_GRANTED) {
_stationPosition.tryEmit(DataState.Loading)
manager.requestLocationUpdates(providerNet, 0L, 0f, this)
} else _stationPosition.tryEmit(DataState.Error(context.getString(R.string.pref_pos_gps_null)))
}
override fun setPositionFromGps() {
val result = ContextCompat.checkSelfPermission(context, locationFine)
if (manager.isProviderEnabled(providerGps) && result == PackageManager.PERMISSION_GRANTED) {
_stationPosition.tryEmit(DataState.Loading)
manager.requestLocationUpdates(providerGps, 0L, 0f, this)
} else _stationPosition.tryEmit(DataState.Error(context.getString(R.string.pref_pos_gps_null)))
}
override fun setPositionFromQth(qthString: String) {
val position = QthConverter.qthToPosition(qthString)
if (position != null) {
setStationPosition(position.latitude, position.longitude)
} else _stationPosition.tryEmit(DataState.Error(context.getString(R.string.pref_pos_qth_error)))
}
override fun onLocationChanged(location: Location) {
manager.removeUpdates(this)
setStationPosition(location.latitude, location.longitude)
}
}
@@ -1,92 +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.Manifest
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.hardware.GeomagneticField
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.core.content.ContextCompat
import com.rtbishop.look4sat.domain.LocationProvider
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.round
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class LocationSource @Inject constructor(
@ApplicationContext private val context: Context,
private val preferences: SharedPreferences
) : LocationListener, LocationProvider {
private val locManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val _updatedLocation = MutableSharedFlow<GeoPos?>()
override val updatedLocation: SharedFlow<GeoPos?> = _updatedLocation
fun getMagDeclination(stationPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
val lat = stationPos.latitude.toFloat()
val lon = stationPos.longitude.toFloat()
return GeomagneticField(lat, lon, 0f, time).declination
}
fun updatePosition(latitude: Double, longitude: Double) {
if (QthConverter.isValidPosition(latitude, longitude)) {
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
} else _updatedLocation.tryEmit(null)
}
fun updatePositionFromQth(qthString: String) {
val position = QthConverter.qthToPosition(qthString)
if (position != null) {
_updatedLocation.tryEmit(GeoPos(position.latitude, position.longitude))
} else _updatedLocation.tryEmit(null)
}
fun updatePositionFromGps() {
val provider = LocationManager.GPS_PROVIDER
val permission = Manifest.permission.ACCESS_FINE_LOCATION
val result = ContextCompat.checkSelfPermission(context, permission)
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
locManager.requestLocationUpdates(provider, 0L, 0f, this)
} else _updatedLocation.tryEmit(null)
}
fun updatePositionFromNetwork() {
val provider = LocationManager.NETWORK_PROVIDER
val permission = Manifest.permission.ACCESS_COARSE_LOCATION
val result = ContextCompat.checkSelfPermission(context, permission)
if (locManager.isProviderEnabled(provider) && result == PackageManager.PERMISSION_GRANTED) {
locManager.requestLocationUpdates(provider, 0L, 0f, this)
} else _updatedLocation.tryEmit(null)
}
override fun onLocationChanged(location: Location) {
locManager.removeUpdates(this)
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
_updatedLocation.tryEmit(GeoPos(latitude, longitude))
}
}
@@ -1,198 +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 android.hardware.GeomagneticField
import android.location.LocationManager
import androidx.core.content.edit
import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.data.PreferencesHandler
import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.round
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PreferencesSource @Inject constructor(
private val locationManager: LocationManager,
private val preferences: SharedPreferences
) : PreferencesHandler {
private val keyInitialSetup = "${BuildConfig.VERSION_NAME}update"
companion object {
const val keyDataSources = "dataSources"
const val keyModes = "satModes"
const val keyCompass = "compass"
const val keyRadarSweep = "radarSweep"
const val keyTimeUTC = "timeUTC"
const val keyHoursAhead = "hoursAhead"
const val keyMinElevation = "minElevation"
const val keyRotator = "isRotatorEnabled"
const val keyRotatorAddress = "rotatorAddress"
const val keyRotatorPort = "rotatorPort"
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
fun loadStationPosition(): GeoPos {
val defaultSP = "0.0"
val latitude = preferences.getString(keyLatitude, null) ?: defaultSP
val longitude = preferences.getString(keyLongitude, null) ?: defaultSP
return GeoPos(latitude.toDouble(), longitude.toDouble())
}
private fun saveStationPosition(pos: GeoPos) {
preferences.edit {
putString(keyLatitude, pos.latitude.toString())
putString(keyLongitude, pos.longitude.toString())
}
}
fun updatePosition(latitude: Double, longitude: Double) {
val stationPosition = GeoPos(latitude, longitude)
saveStationPosition(stationPosition)
}
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 stationPosition = GeoPos(latitude, longitude)
saveStationPosition(stationPosition)
return true
}
} catch (exception: SecurityException) {
false
}
}
fun updatePositionFromQTH(qthString: String): Boolean {
val position = QthConverter.qthToPosition(qthString) ?: return false
val stationPosition = GeoPos(position.latitude, position.longitude)
saveStationPosition(stationPosition)
return true
}
fun getMagDeclination(): Float {
val stationPosition = loadStationPosition()
val lat = stationPosition.latitude.toFloat()
val lon = stationPosition.longitude.toFloat()
return GeomagneticField(lat, lon, 0f, System.currentTimeMillis()).declination
}
fun getHoursAhead(): Int {
return preferences.getInt(keyHoursAhead, 8)
}
fun getMinElevation(): Double {
return preferences.getInt(keyMinElevation, 16).toDouble()
}
fun getUseUTC(): Boolean {
return preferences.getBoolean(keyTimeUTC, false)
}
fun setUseUTC(value: Boolean) {
preferences.edit { putBoolean(keyTimeUTC, value) }
}
fun getUseCompass(): Boolean {
return preferences.getBoolean(keyCompass, true)
}
fun setUseCompass(value: Boolean) {
preferences.edit { putBoolean(keyCompass, value) }
}
fun getShowSweep(): Boolean {
return preferences.getBoolean(keyRadarSweep, true)
}
fun setShowSweep(value: Boolean) {
preferences.edit { putBoolean(keyRadarSweep, value) }
}
fun getSetupDone(): Boolean {
return preferences.getBoolean(keyInitialSetup, false)
}
fun setSetupDone() {
preferences.edit { putBoolean(keyInitialSetup, true) }
}
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 getRotatorEnabled(): Boolean {
return preferences.getBoolean(keyRotator, false)
}
fun setRotatorEnabled(value: Boolean) {
preferences.edit { putBoolean(keyRotator, value) }
}
fun getRotatorServer(): Pair<String, Int> {
val address = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
val port = preferences.getString(keyRotatorPort, null) ?: "4533"
return Pair(address, port.toInt())
}
fun getRotatorIp(): String {
return preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1"
}
fun setRotatorIp(value: String) {
preferences.edit { putString(keyRotatorAddress, value) }
}
fun getRotatorPort(): String {
return preferences.getString(keyRotatorPort, null) ?: "4533"
}
fun setRotatorPort(value: String) {
preferences.edit { putString(keyRotatorPort, value) }
}
override fun loadDataSources(): List<String> {
val sourcesList = preferences.getStringSet(keyDataSources, null)?.toList()
return if (sourcesList.isNullOrEmpty()) defaultSources else sourcesList.sortedDescending()
}
override fun saveDataSources(sources: List<String>) {
if (sources.isNotEmpty()) preferences.edit { putStringSet(keyDataSources, sources.toSet()) }
}
}
@@ -0,0 +1,156 @@
/*
* 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.BuildConfig
import com.rtbishop.look4sat.data.SettingsHandler
import com.rtbishop.look4sat.domain.predict.GeoPos
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SettingsProvider @Inject constructor(private val prefs: SharedPreferences) : SettingsHandler {
private val keyInitialSetup = "${BuildConfig.VERSION_NAME}update"
companion object {
const val keyDataSources = "dataSources"
const val keyModes = "satModes"
const val keyCompass = "compass"
const val keyRadarSweep = "radarSweep"
const val keyTimeUTC = "timeUTC"
const val keyHoursAhead = "hoursAhead"
const val keyMinElevation = "minElevation"
const val keyRotator = "isRotatorEnabled"
const val keyRotatorAddress = "rotatorAddress"
const val keyRotatorPort = "rotatorPort"
const val keyLatitude = "stationLat"
const val keyLongitude = "stationLon"
const val keyPositionGPS = "setPositionGPS"
const val keyPositionQTH = "setPositionQTH"
}
fun loadStationPosition(): GeoPos {
val defaultSP = "0.0"
val latitude = prefs.getString(keyLatitude, null) ?: defaultSP
val longitude = prefs.getString(keyLongitude, null) ?: defaultSP
return GeoPos(latitude.toDouble(), longitude.toDouble())
}
fun saveStationPosition(latitude: Double, longitude: Double) {
prefs.edit {
putString(keyLatitude, latitude.toString())
putString(keyLongitude, longitude.toString())
}
}
fun getHoursAhead(): Int {
return prefs.getInt(keyHoursAhead, 8)
}
fun getMinElevation(): Double {
return prefs.getInt(keyMinElevation, 16).toDouble()
}
fun getUseUTC(): Boolean {
return prefs.getBoolean(keyTimeUTC, false)
}
fun setUseUTC(value: Boolean) {
prefs.edit { putBoolean(keyTimeUTC, value) }
}
fun getUseCompass(): Boolean {
return prefs.getBoolean(keyCompass, true)
}
fun setUseCompass(value: Boolean) {
prefs.edit { putBoolean(keyCompass, value) }
}
fun getShowSweep(): Boolean {
return prefs.getBoolean(keyRadarSweep, true)
}
fun setShowSweep(value: Boolean) {
prefs.edit { putBoolean(keyRadarSweep, value) }
}
fun getSetupDone(): Boolean {
return prefs.getBoolean(keyInitialSetup, false)
}
fun setSetupDone() {
prefs.edit { putBoolean(keyInitialSetup, true) }
}
fun saveModesSelection(modes: List<String>) {
val modesSet = modes.toSet()
prefs.edit {
putStringSet(keyModes, modesSet)
}
}
fun loadModesSelection(): List<String> {
prefs.getStringSet(keyModes, setOf())?.let { modesSet ->
return modesSet.toList().sorted()
}
return emptyList()
}
fun getRotatorEnabled(): Boolean {
return prefs.getBoolean(keyRotator, false)
}
fun setRotatorEnabled(value: Boolean) {
prefs.edit { putBoolean(keyRotator, value) }
}
fun getRotatorServer(): Pair<String, Int> {
val address = prefs.getString(keyRotatorAddress, null) ?: "127.0.0.1"
val port = prefs.getString(keyRotatorPort, null) ?: "4533"
return Pair(address, port.toInt())
}
fun getRotatorIp(): String {
return prefs.getString(keyRotatorAddress, null) ?: "127.0.0.1"
}
fun setRotatorIp(value: String) {
prefs.edit { putString(keyRotatorAddress, value) }
}
fun getRotatorPort(): String {
return prefs.getString(keyRotatorPort, null) ?: "4533"
}
fun setRotatorPort(value: String) {
prefs.edit { putString(keyRotatorPort, value) }
}
override fun loadDataSources(): List<String> {
val sourcesList = prefs.getStringSet(keyDataSources, null)?.toList()
return if (sourcesList.isNullOrEmpty()) defaultSources else sourcesList.sortedDescending()
}
override fun saveDataSources(sources: List<String>) {
if (sources.isNotEmpty()) prefs.edit { putStringSet(keyDataSources, sources.toSet()) }
}
}
@@ -39,9 +39,9 @@ interface EntriesDao {
suspend fun insertEntries(entries: List<SatEntry>) suspend fun insertEntries(entries: List<SatEntry>)
@Transaction @Transaction
suspend fun updateEntries(entries: List<SatEntry>) { suspend fun updateEntries(entries: List<SatEntry>, cleanup: Boolean = false) {
val entriesSelection = getEntriesSelection() val entriesSelection = getEntriesSelection()
deleteEntries() if (cleanup) deleteEntries()
insertEntries(entries) insertEntries(entries)
restoreSelection(entriesSelection, true) restoreSelection(entriesSelection, true)
} }
@@ -19,15 +19,16 @@ package com.rtbishop.look4sat.injection
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.location.LocationManager
import androidx.room.Room import androidx.room.Room
import com.rtbishop.look4sat.data.DefaultRepository import com.rtbishop.look4sat.data.DefaultRepository
import com.rtbishop.look4sat.data.PreferencesHandler import com.rtbishop.look4sat.data.SettingsHandler
import com.rtbishop.look4sat.domain.DataParser import com.rtbishop.look4sat.domain.DataParser
import com.rtbishop.look4sat.domain.DataReporter import com.rtbishop.look4sat.domain.DataReporter
import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.LocationHandler
import com.rtbishop.look4sat.domain.predict.Predictor import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.LocationProvider
import com.rtbishop.look4sat.framework.SettingsProvider
import com.rtbishop.look4sat.framework.local.LocalSource import com.rtbishop.look4sat.framework.local.LocalSource
import com.rtbishop.look4sat.framework.local.SatelliteDb import com.rtbishop.look4sat.framework.local.SatelliteDb
import com.rtbishop.look4sat.framework.remote.RemoteSource import com.rtbishop.look4sat.framework.remote.RemoteSource
@@ -46,7 +47,7 @@ object CoreModule {
@Provides @Provides
@Singleton @Singleton
fun provideSatelliteRepo( fun provideSatelliteRepo(
preferencesHandler: PreferencesHandler, settingsHandler: SettingsHandler,
@ApplicationContext context: Context, @ApplicationContext context: Context,
@IoDispatcher ioDispatcher: CoroutineDispatcher, @IoDispatcher ioDispatcher: CoroutineDispatcher,
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher @DefaultDispatcher defaultDispatcher: CoroutineDispatcher
@@ -56,16 +57,22 @@ object CoreModule {
.fallbackToDestructiveMigration().build() .fallbackToDestructiveMigration().build()
val localSource = LocalSource(db.entriesDao(), db.transmittersDao()) val localSource = LocalSource(db.entriesDao(), db.transmittersDao())
val remoteSource = RemoteSource(ioDispatcher) val remoteSource = RemoteSource(ioDispatcher)
return DefaultRepository(dataParser, localSource, remoteSource, preferencesHandler) return DefaultRepository(dataParser, localSource, remoteSource, settingsHandler)
} }
@Provides @Provides
@Singleton @Singleton
fun providePreferencesHandler( fun provideSettingsHandler(sharedPreferences: SharedPreferences): SettingsHandler {
locationManager: LocationManager, return SettingsProvider(sharedPreferences)
sharedPreferences: SharedPreferences }
): PreferencesHandler {
return PreferencesSource(locationManager, sharedPreferences) @Provides
@Singleton
fun provideLocationHandler(
@ApplicationContext context: Context,
settingsProvider: SettingsProvider
): LocationHandler {
return LocationProvider(context, settingsProvider)
} }
@Provides @Provides
@@ -89,8 +89,6 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) {
val message = getString(R.string.entries_update_error) val message = getString(R.string.entries_update_error)
Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show() Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show()
} }
else -> {
}
} }
} }
@@ -24,7 +24,7 @@ import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.model.SatItem import com.rtbishop.look4sat.domain.model.SatItem
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -34,14 +34,14 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class EntriesViewModel @Inject constructor( class EntriesViewModel @Inject constructor(
private val preferences: PreferencesSource, private val preferences: SettingsProvider,
private val resolver: ContentResolver, private val resolver: ContentResolver,
private val dataRepository: DataRepository private val dataRepository: DataRepository
) : ViewModel(), SearchView.OnQueryTextListener, EntriesAdapter.EntriesClickListener { ) : ViewModel(), SearchView.OnQueryTextListener, EntriesAdapter.EntriesClickListener {
private val coroutineHandler = CoroutineExceptionHandler { _, throwable -> private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
Timber.d(throwable) Timber.d(throwable)
_satData.value = DataState.Error(throwable) _satData.value = DataState.Error(null)
} }
private val transModes = MutableLiveData(preferences.loadModesSelection()) private val transModes = MutableLiveData(preferences.loadModesSelection())
private val currentQuery = MutableLiveData(String()) private val currentQuery = MutableLiveData(String())
@@ -64,7 +64,7 @@ class EntriesViewModel @Inject constructor(
resolver.openInputStream(uri)?.use { stream -> resolver.openInputStream(uri)?.use { stream ->
dataRepository.updateDataFromFile(stream) dataRepository.updateDataFromFile(stream)
} }
}.onFailure { _satData.value = DataState.Error(it) } }.onFailure { _satData.value = DataState.Error(null) }
} }
} }
@@ -23,7 +23,7 @@ import com.rtbishop.look4sat.domain.QthConverter
import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Predictor import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.Satellite import com.rtbishop.look4sat.domain.predict.Satellite
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
@@ -35,7 +35,7 @@ import kotlin.math.min
class MapViewModel @Inject constructor( class MapViewModel @Inject constructor(
private val dataRepository: DataRepository, private val dataRepository: DataRepository,
private val predictor: Predictor, private val predictor: Predictor,
preferences: PreferencesSource, preferences: SettingsProvider,
) : ViewModel() { ) : ViewModel() {
private val stationPos = preferences.loadStationPosition() private val stationPos = preferences.loadStationPosition()
@@ -17,10 +17,8 @@
*/ */
package com.rtbishop.look4sat.presentation.passesScreen package com.rtbishop.look4sat.presentation.passesScreen
import android.Manifest
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -43,15 +41,15 @@ import java.util.*
class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener { class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener {
private val passesViewModel: PassesViewModel by viewModels() private val passesViewModel: PassesViewModel by viewModels()
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions() // private val permReqContract = ActivityResultContracts.RequestMultiplePermissions()
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION // private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION // private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val locPermReq = registerForActivityResult(permReqContract) { permissions -> // private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
when { // when {
permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup() // permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup()
permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup() // permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup()
} // }
} // }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -88,7 +86,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC
handleNewPasses(passesResult, passesAdapter, binding) handleNewPasses(passesResult, passesAdapter, binding)
}) })
passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone -> passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone ->
if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse)) // if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
}) })
getNavResult<Pair<Int, Double>>(R.id.nav_passes, "prefs") { prefs -> getNavResult<Pair<Int, Double>>(R.id.nav_passes, "prefs") { prefs ->
passesViewModel.forceCalculation(prefs.first, prefs.second) passesViewModel.forceCalculation(prefs.first, prefs.second)
@@ -8,7 +8,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.DialogPassesBinding import com.rtbishop.look4sat.databinding.DialogPassesBinding
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@@ -17,7 +17,7 @@ import javax.inject.Inject
class PassesPrefsDialog : AppCompatDialogFragment() { class PassesPrefsDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var preferences: PreferencesSource lateinit var preferences: SettingsProvider
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_passes, group, false) return inflater.inflate(R.layout.dialog_passes, group, false)
@@ -25,7 +25,7 @@ import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.Predictor import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import javax.inject.Inject import javax.inject.Inject
@@ -34,7 +34,7 @@ import javax.inject.Inject
class PassesViewModel @Inject constructor( class PassesViewModel @Inject constructor(
private val dataRepository: DataRepository, private val dataRepository: DataRepository,
private val predictor: Predictor, private val predictor: Predictor,
private val preferences: PreferencesSource private val preferences: SettingsProvider
) : ViewModel() { ) : ViewModel() {
private val _passes = MutableLiveData<DataState<List<SatPass>>>() private val _passes = MutableLiveData<DataState<List<SatPass>>>()
@@ -66,7 +66,7 @@ class PassesViewModel @Inject constructor(
} }
fun triggerInitialSetup() { fun triggerInitialSetup() {
preferences.updatePositionFromGPS() // preferences.updatePositionFromGPS()
viewModelScope.launch { viewModelScope.launch {
// _passes.postValue(DataState.Loading) // _passes.postValue(DataState.Loading)
// val satellites = dataRepository.getSelectedSatellites() // val satellites = dataRepository.getSelectedSatellites()
@@ -115,8 +115,7 @@ class PassesViewModel @Inject constructor(
} }
currentPasses = currentPasses.filter { it.progress < 100 } currentPasses = currentPasses.filter { it.progress < 100 }
val passesCopy = currentPasses.map { it.copy() } val passesCopy = currentPasses.map { it.copy() }
if (passesCopy.isEmpty()) _passes.postValue(DataState.Empty) _passes.postValue(DataState.Success(passesCopy))
else _passes.postValue(DataState.Success(passesCopy))
delay(1000) delay(1000)
} }
} }
@@ -29,7 +29,7 @@ import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentRadarBinding import com.rtbishop.look4sat.databinding.FragmentRadarBinding
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import com.rtbishop.look4sat.presentation.ItemDivider import com.rtbishop.look4sat.presentation.ItemDivider
import com.rtbishop.look4sat.presentation.navigateSafe import com.rtbishop.look4sat.presentation.navigateSafe
import com.rtbishop.look4sat.presentation.toTimerString import com.rtbishop.look4sat.presentation.toTimerString
@@ -40,7 +40,7 @@ import javax.inject.Inject
class RadarFragment : Fragment(R.layout.fragment_radar) { class RadarFragment : Fragment(R.layout.fragment_radar) {
@Inject @Inject
lateinit var preferences: PreferencesSource lateinit var preferences: SettingsProvider
private val viewModel: RadarViewModel by viewModels() private val viewModel: RadarViewModel by viewModels()
private var radarView: RadarView? = null private var radarView: RadarView? = null
@@ -17,15 +17,17 @@
*/ */
package com.rtbishop.look4sat.presentation.radarScreen package com.rtbishop.look4sat.presentation.radarScreen
import android.hardware.GeomagneticField
import androidx.lifecycle.* import androidx.lifecycle.*
import com.rtbishop.look4sat.domain.DataReporter import com.rtbishop.look4sat.domain.DataReporter
import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.domain.predict.Predictor import com.rtbishop.look4sat.domain.predict.Predictor
import com.rtbishop.look4sat.domain.predict.SatPass import com.rtbishop.look4sat.domain.predict.SatPass
import com.rtbishop.look4sat.domain.predict.SatPos import com.rtbishop.look4sat.domain.predict.SatPos
import com.rtbishop.look4sat.domain.model.Transmitter
import com.rtbishop.look4sat.framework.OrientationSource import com.rtbishop.look4sat.framework.OrientationSource
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.framework.SettingsProvider
import com.rtbishop.look4sat.presentation.round import com.rtbishop.look4sat.presentation.round
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -37,7 +39,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RadarViewModel @Inject constructor( class RadarViewModel @Inject constructor(
private val orientationSource: OrientationSource, private val orientationSource: OrientationSource,
private val preferences: PreferencesSource, private val preferences: SettingsProvider,
private val predictor: Predictor, private val predictor: Predictor,
private val dataRepository: DataRepository, private val dataRepository: DataRepository,
private val dataReporter: DataReporter private val dataReporter: DataReporter
@@ -71,7 +73,13 @@ class RadarViewModel @Inject constructor(
} }
override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) { override fun onOrientationChanged(azimuth: Float, pitch: Float, roll: Float) {
_orientation.value = Triple(azimuth + preferences.getMagDeclination(), pitch, roll) _orientation.value = Triple(azimuth + getMagDeclination(stationPos), pitch, roll)
}
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
val latitude = geoPos.latitude.toFloat()
val longitude = geoPos.longitude.toFloat()
return GeomagneticField(latitude, longitude, 0f, time).declination
} }
private fun sendPassData(satPass: SatPass) { private fun sendPassData(satPass: SatPass) {
@@ -18,12 +18,8 @@
package com.rtbishop.look4sat.presentation.settingsScreen package com.rtbishop.look4sat.presentation.settingsScreen
import android.Manifest import android.Manifest
import android.content.Context import android.content.ContentResolver
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
@@ -31,170 +27,161 @@ import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.BuildConfig
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.databinding.FragmentSettingsBinding import com.rtbishop.look4sat.databinding.FragmentSettingsBinding
import com.rtbishop.look4sat.framework.PreferencesSource import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.domain.LocationHandler
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.framework.SettingsProvider
import com.rtbishop.look4sat.presentation.* import com.rtbishop.look4sat.presentation.*
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SettingsFragment : Fragment(R.layout.fragment_settings), LocationListener { class SettingsFragment : Fragment(R.layout.fragment_settings) {
@Inject @Inject
lateinit var preferences: PreferencesSource lateinit var preferences: SettingsProvider
private val viewModel: SettingsViewModel by viewModels() @Inject
private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION lateinit var locationHandler: LocationHandler
private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val contentContract = ActivityResultContracts.GetContent() @Inject
private val filePicker = registerForActivityResult(contentContract) { uri -> lateinit var resolver: ContentResolver
uri?.let { viewModel.updateDataFromFile(uri) }
} @Inject
private val permReqContract = ActivityResultContracts.RequestMultiplePermissions() lateinit var dataRepository: DataRepository
private val locPermReq = registerForActivityResult(permReqContract) { permissions ->
private val locationFine = Manifest.permission.ACCESS_FINE_LOCATION
private val locationCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
private val locationContract = ActivityResultContracts.RequestMultiplePermissions()
private val locationRequest = registerForActivityResult(locationContract) { permissions ->
when { when {
permissions[locPermFine] == true -> updatePositionFromGPS() permissions[locationFine] == true -> locationHandler.setPositionFromLocation()
permissions[locPermCoarse] == true -> updatePositionFromGPS() permissions[locationCoarse] == true -> locationHandler.setPositionFromNet()
else -> showSnack(getString(R.string.pref_pos_gps_error)) else -> showSnack(getString(R.string.pref_pos_gps_error))
} }
} }
private var _binding: FragmentSettingsBinding? = null private val contentContract = ActivityResultContracts.GetContent()
private val contentRequest = registerForActivityResult(contentContract) { uri ->
lifecycleScope.launchWhenResumed {
@Suppress("BlockingMethodInNonBlockingContext")
resolver.openInputStream(uri)?.use { dataRepository.updateDataFromFile(it) }
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentSettingsBinding.bind(view) val settingsBinding = FragmentSettingsBinding.bind(view)
_binding?.prefsBack?.setOnClickListener { findNavController().navigateUp() } settingsBinding.prefsBack.setOnClickListener { findNavController().navigateUp() }
setupAboutCard() setupAboutCard(settingsBinding)
setupDataCard() setupDataCard(settingsBinding)
setupLocationCard() setupLocationCard(settingsBinding)
setupTrackingCard() setupTrackingCard(settingsBinding)
setupOtherCard() setupOtherCard(settingsBinding)
setupWarrantyCard() setupWarrantyCard(settingsBinding)
} locationHandler.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos ->
stationPos?.let { handleStationPosition(it) }
private fun setupAboutCard() {
_binding?.prefsInfo?.let { binding ->
binding.aboutVersion.text =
String.format(getString(R.string.about_version), BuildConfig.VERSION_NAME)
binding.aboutBtnGithub.setOnClickListener {
gotoUrl("https://github.com/rt-bishop/Look4Sat/")
}
binding.aboutBtnDonate.setOnClickListener {
gotoUrl("https://www.buymeacoffee.com/rtbishop")
}
binding.aboutBtnFdroid.setOnClickListener {
gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
}
} }
} }
private fun setupDataCard() { private fun setupAboutCard(binding: FragmentSettingsBinding) {
_binding?.prefsData?.let { binding -> binding.prefsInfo.aboutVersion.text =
binding.updateBtnWeb.setOnClickListener { String.format(getString(R.string.about_version), BuildConfig.VERSION_NAME)
findNavController().navigateSafe(R.id.action_prefs_to_sources) binding.prefsInfo.aboutBtnGithub.setOnClickListener {
} gotoUrl("https://github.com/rt-bishop/Look4Sat/")
binding.updateBtnFile.setOnClickListener { filePicker.launch("*/*") } }
getNavResult<List<String>>(R.id.nav_prefs, "sources") { sources -> binding.prefsInfo.aboutBtnDonate.setOnClickListener {
viewModel.updateDataFromWeb(sources) gotoUrl("https://www.buymeacoffee.com/rtbishop")
} }
binding.prefsInfo.aboutBtnFdroid.setOnClickListener {
gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/")
} }
} }
private fun setupLocationCard() { private fun setupDataCard(binding: FragmentSettingsBinding) {
_binding?.prefsLocation?.let { binding -> binding.prefsData.updateBtnFile.setOnClickListener { contentRequest.launch("*/*") }
binding.locationBtnGps.setOnClickListener { binding.prefsData.updateBtnWeb.setOnClickListener {
updatePositionFromGPS() findNavController().navigateSafe(R.id.action_prefs_to_sources)
} }
binding.locationBtnQth.setOnClickListener { getNavResult<List<String>>(R.id.nav_prefs, "sources") { sources ->
val editText = EditText(requireActivity()) lifecycleScope.launchWhenResumed { dataRepository.updateDataFromWeb(sources) }
AlertDialog.Builder(requireContext())
.setTitle("Title")
.setEditText(editText)
.setPositiveButton("OK") { _, _ ->
val editTextInput = editText.text.toString()
updatePositionFromQth(editTextInput)
}
.setNeutralButton("Cancel", null)
.create()
.show()
}
} }
} }
private fun setupTrackingCard() { private fun setupLocationCard(binding: FragmentSettingsBinding) {
_binding?.prefsTracking?.let { binding -> binding.prefsLocation.locationBtnGps.setOnClickListener {
binding.trackingSwitch.apply { locationRequest.launch(arrayOf(locationFine, locationCoarse))
isChecked = preferences.getRotatorEnabled() }
binding.trackingIp.isEnabled = isChecked binding.prefsLocation.locationBtnQth.setOnClickListener {
binding.trackingIpEdit.setText(preferences.getRotatorIp()) val editText = EditText(requireActivity())
binding.trackingPort.isEnabled = isChecked AlertDialog.Builder(requireContext())
binding.trackingPortEdit.setText(preferences.getRotatorPort()) .setTitle("Title")
setOnCheckedChangeListener { _, isChecked -> .setEditText(editText)
preferences.setRotatorEnabled(isChecked) .setPositiveButton("OK") { _, _ ->
binding.trackingIp.isEnabled = isChecked val editTextInput = editText.text.toString()
binding.trackingPort.isEnabled = isChecked locationHandler.setPositionFromQth(editTextInput)
} }
} .setNeutralButton("Cancel", null)
binding.trackingIpEdit.doOnTextChanged { text, _, _, _ -> .create()
if (text.toString().isValidIPv4()) preferences.setRotatorIp(text.toString()) .show()
}
binding.trackingPortEdit.doOnTextChanged { text, _, _, _ ->
if (text.toString().isValidPort()) preferences.setRotatorPort(text.toString())
}
} }
} }
private fun setupOtherCard() { private fun setupTrackingCard(binding: FragmentSettingsBinding) {
_binding?.prefsOther?.let { binding -> binding.prefsTracking.trackingSwitch.apply {
binding.otherSwitchUtc.apply { isChecked = preferences.getRotatorEnabled()
isChecked = preferences.getUseUTC() binding.prefsTracking.trackingIp.isEnabled = isChecked
setOnCheckedChangeListener { _, isChecked -> preferences.setUseUTC(isChecked) } binding.prefsTracking.trackingIpEdit.setText(preferences.getRotatorIp())
} binding.prefsTracking.trackingPort.isEnabled = isChecked
binding.otherSwitchSweep.apply { binding.prefsTracking.trackingPortEdit.setText(preferences.getRotatorPort())
isChecked = preferences.getShowSweep() setOnCheckedChangeListener { _, isChecked ->
setOnCheckedChangeListener { _, isChecked -> preferences.setShowSweep(isChecked) } preferences.setRotatorEnabled(isChecked)
} binding.prefsTracking.trackingIp.isEnabled = isChecked
binding.otherSwitchSensors.apply { binding.prefsTracking.trackingPort.isEnabled = isChecked
isChecked = preferences.getUseCompass()
setOnCheckedChangeListener { _, isChecked -> preferences.setUseCompass(isChecked) }
} }
} }
binding.prefsTracking.trackingIpEdit.doOnTextChanged { text, _, _, _ ->
if (text.toString().isValidIPv4()) preferences.setRotatorIp(text.toString())
}
binding.prefsTracking.trackingPortEdit.doOnTextChanged { text, _, _, _ ->
if (text.toString().isValidPort()) preferences.setRotatorPort(text.toString())
}
} }
private fun setupWarrantyCard() { private fun setupOtherCard(binding: FragmentSettingsBinding) {
_binding?.prefsWarranty?.let { binding -> binding.prefsOther.otherSwitchUtc.apply {
binding.warrantyThanks.movementMethod = LinkMovementMethod.getInstance() isChecked = preferences.getUseUTC()
binding.warrantyLicense.movementMethod = LinkMovementMethod.getInstance() setOnCheckedChangeListener { _, isChecked -> preferences.setUseUTC(isChecked) }
}
binding.prefsOther.otherSwitchSweep.apply {
isChecked = preferences.getShowSweep()
setOnCheckedChangeListener { _, isChecked -> preferences.setShowSweep(isChecked) }
}
binding.prefsOther.otherSwitchSensors.apply {
isChecked = preferences.getUseCompass()
setOnCheckedChangeListener { _, isChecked -> preferences.setUseCompass(isChecked) }
} }
} }
private fun updatePositionFromGPS() { private fun setupWarrantyCard(binding: FragmentSettingsBinding) {
val manager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager binding.prefsWarranty.warrantyThanks.movementMethod = LinkMovementMethod.getInstance()
val provider = LocationManager.NETWORK_PROVIDER binding.prefsWarranty.warrantyLicense.movementMethod = LinkMovementMethod.getInstance()
val permission = Manifest.permission.ACCESS_COARSE_LOCATION
val result = ContextCompat.checkSelfPermission(requireContext(), permission)
if (result == PackageManager.PERMISSION_GRANTED) {
if (manager.isProviderEnabled(provider)) {
manager.requestLocationUpdates(provider, 0L, 0f, this)
} else {
showSnack(getString(R.string.pref_pos_gps_null))
}
} else locPermReq.launch(arrayOf(locPermFine, locPermCoarse))
} }
private fun updatePositionFromQth(qthString: String) { private fun handleStationPosition(position: DataState<GeoPos>) {
if (preferences.updatePositionFromQTH(qthString)) { when (position) {
showSnack(getString(R.string.pref_pos_success)) is DataState.Success -> showSnack(getString(R.string.pref_pos_success))
} else { is DataState.Error -> showSnack(position.message.toString())
showSnack(getString(R.string.pref_pos_qth_error)) DataState.Loading -> showSnack("Fetching position")
} }
} }
@@ -205,18 +192,4 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), LocationListener
private fun gotoUrl(url: String) { private fun gotoUrl(url: String) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} }
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
override fun onLocationChanged(location: Location) {
val manager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
manager.removeUpdates(this)
val latitude = location.latitude.round(4)
val longitude = location.longitude.round(4)
preferences.updatePosition(latitude, longitude)
showSnack(getString(R.string.pref_pos_success))
}
} }
@@ -1,62 +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.settingsScreen
import android.content.ContentResolver
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.rtbishop.look4sat.domain.DataRepository
import com.rtbishop.look4sat.framework.PreferencesSource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val preferences: PreferencesSource,
private val resolver: ContentResolver,
private val dataRepository: DataRepository
) : ViewModel() {
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
// Timber.d(throwable)
// _satData.value = DataState.Error(throwable)
}
fun updateDataFromFile(uri: Uri) {
viewModelScope.launch(coroutineHandler) {
// _satData.value = DataState.Loading
runCatching {
resolver.openInputStream(uri)?.use { stream ->
dataRepository.updateDataFromFile(stream)
}
}.onFailure {
// _satData.value = DataState.Error(it)
}
}
}
fun updateDataFromWeb(sources: List<String>) {
viewModelScope.launch(coroutineHandler) {
// _satData.value = DataState.Loading
dataRepository.updateDataFromWeb(sources)
}
}
}
@@ -25,7 +25,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.app.AppCompatDialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.rtbishop.look4sat.R import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.data.PreferencesHandler import com.rtbishop.look4sat.data.SettingsHandler
import com.rtbishop.look4sat.databinding.DialogSourcesBinding import com.rtbishop.look4sat.databinding.DialogSourcesBinding
import com.rtbishop.look4sat.framework.model.Source import com.rtbishop.look4sat.framework.model.Source
import com.rtbishop.look4sat.presentation.setNavResult import com.rtbishop.look4sat.presentation.setNavResult
@@ -36,7 +36,7 @@ import javax.inject.Inject
class SourcesDialog : AppCompatDialogFragment() { class SourcesDialog : AppCompatDialogFragment() {
@Inject @Inject
lateinit var preferences: PreferencesHandler lateinit var settings: SettingsHandler
override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? {
return inflater.inflate(R.layout.dialog_sources, group, false) return inflater.inflate(R.layout.dialog_sources, group, false)
@@ -44,7 +44,7 @@ class SourcesDialog : AppCompatDialogFragment() {
override fun onViewCreated(view: View, state: Bundle?) { override fun onViewCreated(view: View, state: Bundle?) {
super.onViewCreated(view, state) super.onViewCreated(view, state)
val sources = preferences.loadDataSources() val sources = settings.loadDataSources()
val sourcesAdapter = SourcesAdapter().apply { setSources(sources.map { Source(it) }) } val sourcesAdapter = SourcesAdapter().apply { setSources(sources.map { Source(it) }) }
DialogSourcesBinding.bind(view).apply { DialogSourcesBinding.bind(view).apply {
dialog?.window?.setLayout( dialog?.window?.setLayout(
+1 -1
View File
@@ -15,7 +15,7 @@ buildscript {
timber_version = '5.0.1' timber_version = '5.0.1'
junit_version = '4.13.2' junit_version = '4.13.2'
mockito_version = '4.2.0' mockito_version = '4.2.0'
leak_canary_version = '2.7' leak_canary_version = '2.8'
} }
repositories { repositories {
google() google()
@@ -32,7 +32,7 @@ class DefaultRepository(
private val dataParser: DataParser, private val dataParser: DataParser,
private val localSource: LocalDataSource, private val localSource: LocalDataSource,
private val remoteSource: RemoteDataSource, private val remoteSource: RemoteDataSource,
private val preferences: PreferencesHandler private val settings: SettingsHandler
) : DataRepository { ) : DataRepository {
override fun getSatelliteItems() = localSource.getSatelliteItems() override fun getSatelliteItems() = localSource.getSatelliteItems()
@@ -48,7 +48,7 @@ class DefaultRepository(
override suspend fun updateDataFromWeb(sources: List<String>) { override suspend fun updateDataFromWeb(sources: List<String>) {
coroutineScope { coroutineScope {
launch { launch {
preferences.saveDataSources(sources) settings.saveDataSources(sources)
} }
launch { launch {
val updateTimeMillis = measureTimeMillis { val updateTimeMillis = measureTimeMillis {
@@ -78,7 +78,7 @@ class DefaultRepository(
println("Update from web took $updateTimeMillis ms") println("Update from web took $updateTimeMillis ms")
} }
launch { launch {
remoteSource.fetchFileStream(preferences.transmittersSource)?.let { inputStream -> remoteSource.fetchFileStream(settings.transmittersSource)?.let { inputStream ->
val transmitters = dataParser.parseJSONStream(inputStream) val transmitters = dataParser.parseJSONStream(inputStream)
localSource.updateTransmitters(transmitters) localSource.updateTransmitters(transmitters)
} }
@@ -17,7 +17,7 @@
*/ */
package com.rtbishop.look4sat.data package com.rtbishop.look4sat.data
interface PreferencesHandler { interface SettingsHandler {
val defaultSources: List<String> val defaultSources: List<String>
get() = listOf( get() = listOf(
@@ -0,0 +1,22 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.model.DataState
import com.rtbishop.look4sat.domain.predict.GeoPos
import kotlinx.coroutines.flow.SharedFlow
interface LocationHandler {
val stationPosition: SharedFlow<DataState<GeoPos>>
fun getStationPosition(): GeoPos
fun setStationPosition(latitude: Double, longitude: Double)
fun setPositionFromLocation()
fun setPositionFromNet()
fun setPositionFromGps()
fun setPositionFromQth(qthString: String)
}
@@ -1,9 +0,0 @@
package com.rtbishop.look4sat.domain
import com.rtbishop.look4sat.domain.predict.GeoPos
import kotlinx.coroutines.flow.SharedFlow
interface LocationProvider {
val updatedLocation: SharedFlow<GeoPos?>
}
@@ -19,7 +19,6 @@ package com.rtbishop.look4sat.domain.model
sealed class DataState<out T> { sealed class DataState<out T> {
data class Success<out T>(val data: T) : DataState<T>() data class Success<out T>(val data: T) : DataState<T>()
data class Error(val error: Throwable) : DataState<Nothing>() data class Error(val message: String?) : DataState<Nothing>()
object Empty : DataState<Nothing>()
object Loading : DataState<Nothing>() object Loading : DataState<Nothing>()
} }
@@ -84,8 +84,6 @@ class DataParserTest {
fun `Given valid data streams parsed results match`() = runTest(testDispatcher) { fun `Given valid data streams parsed results match`() = runTest(testDispatcher) {
val csvResult = dataParser.parseCSVStream(validCSVStream) val csvResult = dataParser.parseCSVStream(validCSVStream)
val tleResult = dataParser.parseTLEStream(validTLEStream) val tleResult = dataParser.parseTLEStream(validTLEStream)
println(csvResult)
println(tleResult)
assert(csvResult == tleResult) assert(csvResult == tleResult)
} }