From a9a4aa9737b52baf7bc7028474c194db88081119 Mon Sep 17 00:00:00 2001 From: Arty Bishop Date: Sat, 8 Jan 2022 21:25:21 +0000 Subject: [PATCH] Switched to a generic LocationHandler.kt --- .../look4sat/framework/LocationProvider.kt | 108 ++++++++ .../look4sat/framework/LocationSource.kt | 92 ------- .../look4sat/framework/PreferencesSource.kt | 198 -------------- .../look4sat/framework/SettingsProvider.kt | 156 +++++++++++ .../look4sat/framework/local/EntriesDao.kt | 4 +- .../rtbishop/look4sat/injection/CoreModule.kt | 27 +- .../entriesScreen/EntriesFragment.kt | 2 - .../entriesScreen/EntriesViewModel.kt | 8 +- .../presentation/mapScreen/MapViewModel.kt | 4 +- .../passesScreen/PassesFragment.kt | 22 +- .../passesScreen/PassesPrefsDialog.kt | 4 +- .../passesScreen/PassesViewModel.kt | 9 +- .../presentation/radarScreen/RadarFragment.kt | 4 +- .../radarScreen/RadarViewModel.kt | 16 +- .../settingsScreen/SettingsFragment.kt | 251 ++++++++---------- .../settingsScreen/SettingsViewModel.kt | 62 ----- .../sourcesScreen/SourcesDialog.kt | 6 +- build.gradle | 2 +- .../look4sat/data/DefaultRepository.kt | 6 +- ...eferencesHandler.kt => SettingsHandler.kt} | 2 +- .../look4sat/domain/LocationHandler.kt | 22 ++ .../look4sat/domain/LocationProvider.kt | 9 - .../look4sat/domain/model/DataState.kt | 3 +- .../com/rtbishop/look4sat/DataParserTest.kt | 2 - 24 files changed, 462 insertions(+), 557 deletions(-) create mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/LocationProvider.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt create mode 100644 app/src/main/java/com/rtbishop/look4sat/framework/SettingsProvider.kt delete mode 100644 app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt rename core/src/main/java/com/rtbishop/look4sat/data/{PreferencesHandler.kt => SettingsHandler.kt} (97%) create mode 100644 core/src/main/java/com/rtbishop/look4sat/domain/LocationHandler.kt delete mode 100644 core/src/main/java/com/rtbishop/look4sat/domain/LocationProvider.kt diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/LocationProvider.kt b/app/src/main/java/com/rtbishop/look4sat/framework/LocationProvider.kt new file mode 100644 index 00000000..199636a4 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/framework/LocationProvider.kt @@ -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 . + */ +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>( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + private var currentPosition = settingsProvider.loadStationPosition() + override val stationPosition: SharedFlow> = _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) + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt deleted file mode 100644 index 0766904d..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/framework/LocationSource.kt +++ /dev/null @@ -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 . - */ -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() - override val updatedLocation: SharedFlow = _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)) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt b/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt deleted file mode 100644 index b69dd601..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/framework/PreferencesSource.kt +++ /dev/null @@ -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 . - */ -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) { - val modesSet = modes.toSet() - preferences.edit { - putStringSet(keyModes, modesSet) - } - } - - fun loadModesSelection(): List { - 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 { - 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 { - val sourcesList = preferences.getStringSet(keyDataSources, null)?.toList() - return if (sourcesList.isNullOrEmpty()) defaultSources else sourcesList.sortedDescending() - } - - override fun saveDataSources(sources: List) { - if (sources.isNotEmpty()) preferences.edit { putStringSet(keyDataSources, sources.toSet()) } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/SettingsProvider.kt b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsProvider.kt new file mode 100644 index 00000000..6f0facb9 --- /dev/null +++ b/app/src/main/java/com/rtbishop/look4sat/framework/SettingsProvider.kt @@ -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 . + */ +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) { + val modesSet = modes.toSet() + prefs.edit { + putStringSet(keyModes, modesSet) + } + } + + fun loadModesSelection(): List { + 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 { + 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 { + val sourcesList = prefs.getStringSet(keyDataSources, null)?.toList() + return if (sourcesList.isNullOrEmpty()) defaultSources else sourcesList.sortedDescending() + } + + override fun saveDataSources(sources: List) { + if (sources.isNotEmpty()) prefs.edit { putStringSet(keyDataSources, sources.toSet()) } + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/framework/local/EntriesDao.kt b/app/src/main/java/com/rtbishop/look4sat/framework/local/EntriesDao.kt index 12fb6f78..147c224d 100644 --- a/app/src/main/java/com/rtbishop/look4sat/framework/local/EntriesDao.kt +++ b/app/src/main/java/com/rtbishop/look4sat/framework/local/EntriesDao.kt @@ -39,9 +39,9 @@ interface EntriesDao { suspend fun insertEntries(entries: List) @Transaction - suspend fun updateEntries(entries: List) { + suspend fun updateEntries(entries: List, cleanup: Boolean = false) { val entriesSelection = getEntriesSelection() - deleteEntries() + if (cleanup) deleteEntries() insertEntries(entries) restoreSelection(entriesSelection, true) } diff --git a/app/src/main/java/com/rtbishop/look4sat/injection/CoreModule.kt b/app/src/main/java/com/rtbishop/look4sat/injection/CoreModule.kt index 89f5c1b7..5710decb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/injection/CoreModule.kt +++ b/app/src/main/java/com/rtbishop/look4sat/injection/CoreModule.kt @@ -19,15 +19,16 @@ package com.rtbishop.look4sat.injection import android.content.Context import android.content.SharedPreferences -import android.location.LocationManager import androidx.room.Room 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.DataReporter import com.rtbishop.look4sat.domain.DataRepository +import com.rtbishop.look4sat.domain.LocationHandler 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.SatelliteDb import com.rtbishop.look4sat.framework.remote.RemoteSource @@ -46,7 +47,7 @@ object CoreModule { @Provides @Singleton fun provideSatelliteRepo( - preferencesHandler: PreferencesHandler, + settingsHandler: SettingsHandler, @ApplicationContext context: Context, @IoDispatcher ioDispatcher: CoroutineDispatcher, @DefaultDispatcher defaultDispatcher: CoroutineDispatcher @@ -56,16 +57,22 @@ object CoreModule { .fallbackToDestructiveMigration().build() val localSource = LocalSource(db.entriesDao(), db.transmittersDao()) val remoteSource = RemoteSource(ioDispatcher) - return DefaultRepository(dataParser, localSource, remoteSource, preferencesHandler) + return DefaultRepository(dataParser, localSource, remoteSource, settingsHandler) } @Provides @Singleton - fun providePreferencesHandler( - locationManager: LocationManager, - sharedPreferences: SharedPreferences - ): PreferencesHandler { - return PreferencesSource(locationManager, sharedPreferences) + fun provideSettingsHandler(sharedPreferences: SharedPreferences): SettingsHandler { + return SettingsProvider(sharedPreferences) + } + + @Provides + @Singleton + fun provideLocationHandler( + @ApplicationContext context: Context, + settingsProvider: SettingsProvider + ): LocationHandler { + return LocationProvider(context, settingsProvider) } @Provides diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesFragment.kt index 8f2bb2ac..8b64ae3d 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesFragment.kt @@ -89,8 +89,6 @@ class EntriesFragment : Fragment(R.layout.fragment_entries) { val message = getString(R.string.entries_update_error) Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT).show() } - else -> { - } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesViewModel.kt index 1083fdbc..440fc2e5 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/entriesScreen/EntriesViewModel.kt @@ -24,7 +24,7 @@ import androidx.lifecycle.* import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.domain.model.DataState 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 kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.launch @@ -34,14 +34,14 @@ import javax.inject.Inject @HiltViewModel class EntriesViewModel @Inject constructor( - private val preferences: PreferencesSource, + private val preferences: SettingsProvider, private val resolver: ContentResolver, private val dataRepository: DataRepository ) : ViewModel(), SearchView.OnQueryTextListener, EntriesAdapter.EntriesClickListener { private val coroutineHandler = CoroutineExceptionHandler { _, throwable -> Timber.d(throwable) - _satData.value = DataState.Error(throwable) + _satData.value = DataState.Error(null) } private val transModes = MutableLiveData(preferences.loadModesSelection()) private val currentQuery = MutableLiveData(String()) @@ -64,7 +64,7 @@ class EntriesViewModel @Inject constructor( resolver.openInputStream(uri)?.use { stream -> dataRepository.updateDataFromFile(stream) } - }.onFailure { _satData.value = DataState.Error(it) } + }.onFailure { _satData.value = DataState.Error(null) } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt index 2bf6e576..e6f2cd04 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/mapScreen/MapViewModel.kt @@ -23,7 +23,7 @@ import com.rtbishop.look4sat.domain.QthConverter import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.Predictor 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 kotlinx.coroutines.* import java.util.* @@ -35,7 +35,7 @@ import kotlin.math.min class MapViewModel @Inject constructor( private val dataRepository: DataRepository, private val predictor: Predictor, - preferences: PreferencesSource, + preferences: SettingsProvider, ) : ViewModel() { private val stationPos = preferences.loadStationPosition() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt index 55b4fd86..1c7f8681 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesFragment.kt @@ -17,10 +17,8 @@ */ package com.rtbishop.look4sat.presentation.passesScreen -import android.Manifest import android.os.Bundle import android.view.View -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -43,15 +41,15 @@ import java.util.* class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesClickListener { private val passesViewModel: PassesViewModel by viewModels() - private val permReqContract = ActivityResultContracts.RequestMultiplePermissions() - private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION - private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION - private val locPermReq = registerForActivityResult(permReqContract) { permissions -> - when { - permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup() - permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup() - } - } +// private val permReqContract = ActivityResultContracts.RequestMultiplePermissions() +// private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION +// private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION +// private val locPermReq = registerForActivityResult(permReqContract) { permissions -> +// when { +// permissions[locPermFine] == true -> passesViewModel.triggerInitialSetup() +// permissions[locPermCoarse] == true -> passesViewModel.triggerInitialSetup() +// } +// } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -88,7 +86,7 @@ class PassesFragment : Fragment(R.layout.fragment_passes), PassesAdapter.PassesC handleNewPasses(passesResult, passesAdapter, binding) }) passesViewModel.isFirstLaunchDone.observe(viewLifecycleOwner, { setupDone -> - if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse)) +// if (!setupDone) locPermReq.launch(arrayOf(locPermFine, locPermCoarse)) }) getNavResult>(R.id.nav_passes, "prefs") { prefs -> passesViewModel.forceCalculation(prefs.first, prefs.second) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesPrefsDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesPrefsDialog.kt index 154d034e..2b070ffb 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesPrefsDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesPrefsDialog.kt @@ -8,7 +8,7 @@ import android.view.WindowManager import androidx.appcompat.app.AppCompatDialogFragment import com.rtbishop.look4sat.R 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 dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @@ -17,7 +17,7 @@ import javax.inject.Inject class PassesPrefsDialog : AppCompatDialogFragment() { @Inject - lateinit var preferences: PreferencesSource + lateinit var preferences: SettingsProvider override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { return inflater.inflate(R.layout.dialog_passes, group, false) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt index 2d6bbf4a..9e55afa4 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passesScreen/PassesViewModel.kt @@ -25,7 +25,7 @@ import com.rtbishop.look4sat.domain.DataRepository import com.rtbishop.look4sat.domain.model.DataState import com.rtbishop.look4sat.domain.predict.Predictor 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 kotlinx.coroutines.* import javax.inject.Inject @@ -34,7 +34,7 @@ import javax.inject.Inject class PassesViewModel @Inject constructor( private val dataRepository: DataRepository, private val predictor: Predictor, - private val preferences: PreferencesSource + private val preferences: SettingsProvider ) : ViewModel() { private val _passes = MutableLiveData>>() @@ -66,7 +66,7 @@ class PassesViewModel @Inject constructor( } fun triggerInitialSetup() { - preferences.updatePositionFromGPS() +// preferences.updatePositionFromGPS() viewModelScope.launch { // _passes.postValue(DataState.Loading) // val satellites = dataRepository.getSelectedSatellites() @@ -115,8 +115,7 @@ class PassesViewModel @Inject constructor( } currentPasses = currentPasses.filter { it.progress < 100 } val passesCopy = currentPasses.map { it.copy() } - if (passesCopy.isEmpty()) _passes.postValue(DataState.Empty) - else _passes.postValue(DataState.Success(passesCopy)) + _passes.postValue(DataState.Success(passesCopy)) delay(1000) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt index 0863e9cd..a62f369f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarFragment.kt @@ -29,7 +29,7 @@ import com.rtbishop.look4sat.R import com.rtbishop.look4sat.databinding.FragmentRadarBinding import com.rtbishop.look4sat.domain.predict.SatPass 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.navigateSafe import com.rtbishop.look4sat.presentation.toTimerString @@ -40,7 +40,7 @@ import javax.inject.Inject class RadarFragment : Fragment(R.layout.fragment_radar) { @Inject - lateinit var preferences: PreferencesSource + lateinit var preferences: SettingsProvider private val viewModel: RadarViewModel by viewModels() private var radarView: RadarView? = null diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt index aa8d37eb..e5f294e1 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/radarScreen/RadarViewModel.kt @@ -17,15 +17,17 @@ */ package com.rtbishop.look4sat.presentation.radarScreen +import android.hardware.GeomagneticField import androidx.lifecycle.* import com.rtbishop.look4sat.domain.DataReporter 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.SatPass 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.PreferencesSource +import com.rtbishop.look4sat.framework.SettingsProvider import com.rtbishop.look4sat.presentation.round import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay @@ -37,7 +39,7 @@ import javax.inject.Inject @HiltViewModel class RadarViewModel @Inject constructor( private val orientationSource: OrientationSource, - private val preferences: PreferencesSource, + private val preferences: SettingsProvider, private val predictor: Predictor, private val dataRepository: DataRepository, private val dataReporter: DataReporter @@ -71,7 +73,13 @@ class RadarViewModel @Inject constructor( } 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) { diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt index 03134d11..e7cb082a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsFragment.kt @@ -18,12 +18,8 @@ package com.rtbishop.look4sat.presentation.settingsScreen import android.Manifest -import android.content.Context +import android.content.ContentResolver 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.os.Bundle import android.text.method.LinkMovementMethod @@ -31,170 +27,161 @@ import android.view.View import android.widget.EditText import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.lifecycle.asLiveData +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.snackbar.Snackbar import com.rtbishop.look4sat.BuildConfig import com.rtbishop.look4sat.R 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 dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint -class SettingsFragment : Fragment(R.layout.fragment_settings), LocationListener { +class SettingsFragment : Fragment(R.layout.fragment_settings) { @Inject - lateinit var preferences: PreferencesSource + lateinit var preferences: SettingsProvider - private val viewModel: SettingsViewModel by viewModels() - private val locPermFine = Manifest.permission.ACCESS_FINE_LOCATION - private val locPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION - private val contentContract = ActivityResultContracts.GetContent() - private val filePicker = registerForActivityResult(contentContract) { uri -> - uri?.let { viewModel.updateDataFromFile(uri) } - } - private val permReqContract = ActivityResultContracts.RequestMultiplePermissions() - private val locPermReq = registerForActivityResult(permReqContract) { permissions -> + @Inject + lateinit var locationHandler: LocationHandler + + @Inject + lateinit var resolver: ContentResolver + + @Inject + lateinit var dataRepository: DataRepository + + 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 { - permissions[locPermFine] == true -> updatePositionFromGPS() - permissions[locPermCoarse] == true -> updatePositionFromGPS() + permissions[locationFine] == true -> locationHandler.setPositionFromLocation() + permissions[locationCoarse] == true -> locationHandler.setPositionFromNet() 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?) { super.onViewCreated(view, savedInstanceState) - _binding = FragmentSettingsBinding.bind(view) - _binding?.prefsBack?.setOnClickListener { findNavController().navigateUp() } - setupAboutCard() - setupDataCard() - setupLocationCard() - setupTrackingCard() - setupOtherCard() - setupWarrantyCard() - } - - 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/") - } + val settingsBinding = FragmentSettingsBinding.bind(view) + settingsBinding.prefsBack.setOnClickListener { findNavController().navigateUp() } + setupAboutCard(settingsBinding) + setupDataCard(settingsBinding) + setupLocationCard(settingsBinding) + setupTrackingCard(settingsBinding) + setupOtherCard(settingsBinding) + setupWarrantyCard(settingsBinding) + locationHandler.stationPosition.asLiveData().observe(viewLifecycleOwner) { stationPos -> + stationPos?.let { handleStationPosition(it) } } } - private fun setupDataCard() { - _binding?.prefsData?.let { binding -> - binding.updateBtnWeb.setOnClickListener { - findNavController().navigateSafe(R.id.action_prefs_to_sources) - } - binding.updateBtnFile.setOnClickListener { filePicker.launch("*/*") } - getNavResult>(R.id.nav_prefs, "sources") { sources -> - viewModel.updateDataFromWeb(sources) - } + private fun setupAboutCard(binding: FragmentSettingsBinding) { + binding.prefsInfo.aboutVersion.text = + String.format(getString(R.string.about_version), BuildConfig.VERSION_NAME) + binding.prefsInfo.aboutBtnGithub.setOnClickListener { + gotoUrl("https://github.com/rt-bishop/Look4Sat/") + } + binding.prefsInfo.aboutBtnDonate.setOnClickListener { + gotoUrl("https://www.buymeacoffee.com/rtbishop") + } + binding.prefsInfo.aboutBtnFdroid.setOnClickListener { + gotoUrl("https://f-droid.org/en/packages/com.rtbishop.look4sat/") } } - private fun setupLocationCard() { - _binding?.prefsLocation?.let { binding -> - binding.locationBtnGps.setOnClickListener { - updatePositionFromGPS() - } - binding.locationBtnQth.setOnClickListener { - val editText = EditText(requireActivity()) - AlertDialog.Builder(requireContext()) - .setTitle("Title") - .setEditText(editText) - .setPositiveButton("OK") { _, _ -> - val editTextInput = editText.text.toString() - updatePositionFromQth(editTextInput) - } - .setNeutralButton("Cancel", null) - .create() - .show() - } + private fun setupDataCard(binding: FragmentSettingsBinding) { + binding.prefsData.updateBtnFile.setOnClickListener { contentRequest.launch("*/*") } + binding.prefsData.updateBtnWeb.setOnClickListener { + findNavController().navigateSafe(R.id.action_prefs_to_sources) + } + getNavResult>(R.id.nav_prefs, "sources") { sources -> + lifecycleScope.launchWhenResumed { dataRepository.updateDataFromWeb(sources) } } } - private fun setupTrackingCard() { - _binding?.prefsTracking?.let { binding -> - binding.trackingSwitch.apply { - isChecked = preferences.getRotatorEnabled() - binding.trackingIp.isEnabled = isChecked - binding.trackingIpEdit.setText(preferences.getRotatorIp()) - binding.trackingPort.isEnabled = isChecked - binding.trackingPortEdit.setText(preferences.getRotatorPort()) - setOnCheckedChangeListener { _, isChecked -> - preferences.setRotatorEnabled(isChecked) - binding.trackingIp.isEnabled = isChecked - binding.trackingPort.isEnabled = isChecked + private fun setupLocationCard(binding: FragmentSettingsBinding) { + binding.prefsLocation.locationBtnGps.setOnClickListener { + locationRequest.launch(arrayOf(locationFine, locationCoarse)) + } + binding.prefsLocation.locationBtnQth.setOnClickListener { + val editText = EditText(requireActivity()) + AlertDialog.Builder(requireContext()) + .setTitle("Title") + .setEditText(editText) + .setPositiveButton("OK") { _, _ -> + val editTextInput = editText.text.toString() + locationHandler.setPositionFromQth(editTextInput) } - } - binding.trackingIpEdit.doOnTextChanged { text, _, _, _ -> - if (text.toString().isValidIPv4()) preferences.setRotatorIp(text.toString()) - } - binding.trackingPortEdit.doOnTextChanged { text, _, _, _ -> - if (text.toString().isValidPort()) preferences.setRotatorPort(text.toString()) - } + .setNeutralButton("Cancel", null) + .create() + .show() } } - private fun setupOtherCard() { - _binding?.prefsOther?.let { binding -> - binding.otherSwitchUtc.apply { - isChecked = preferences.getUseUTC() - setOnCheckedChangeListener { _, isChecked -> preferences.setUseUTC(isChecked) } - } - binding.otherSwitchSweep.apply { - isChecked = preferences.getShowSweep() - setOnCheckedChangeListener { _, isChecked -> preferences.setShowSweep(isChecked) } - } - binding.otherSwitchSensors.apply { - isChecked = preferences.getUseCompass() - setOnCheckedChangeListener { _, isChecked -> preferences.setUseCompass(isChecked) } + private fun setupTrackingCard(binding: FragmentSettingsBinding) { + binding.prefsTracking.trackingSwitch.apply { + isChecked = preferences.getRotatorEnabled() + binding.prefsTracking.trackingIp.isEnabled = isChecked + binding.prefsTracking.trackingIpEdit.setText(preferences.getRotatorIp()) + binding.prefsTracking.trackingPort.isEnabled = isChecked + binding.prefsTracking.trackingPortEdit.setText(preferences.getRotatorPort()) + setOnCheckedChangeListener { _, isChecked -> + preferences.setRotatorEnabled(isChecked) + binding.prefsTracking.trackingIp.isEnabled = isChecked + binding.prefsTracking.trackingPort.isEnabled = 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() { - _binding?.prefsWarranty?.let { binding -> - binding.warrantyThanks.movementMethod = LinkMovementMethod.getInstance() - binding.warrantyLicense.movementMethod = LinkMovementMethod.getInstance() + private fun setupOtherCard(binding: FragmentSettingsBinding) { + binding.prefsOther.otherSwitchUtc.apply { + isChecked = preferences.getUseUTC() + 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() { - val manager = requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager - val provider = LocationManager.NETWORK_PROVIDER - 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 setupWarrantyCard(binding: FragmentSettingsBinding) { + binding.prefsWarranty.warrantyThanks.movementMethod = LinkMovementMethod.getInstance() + binding.prefsWarranty.warrantyLicense.movementMethod = LinkMovementMethod.getInstance() } - private fun updatePositionFromQth(qthString: String) { - if (preferences.updatePositionFromQTH(qthString)) { - showSnack(getString(R.string.pref_pos_success)) - } else { - showSnack(getString(R.string.pref_pos_qth_error)) + private fun handleStationPosition(position: DataState) { + when (position) { + is DataState.Success -> showSnack(getString(R.string.pref_pos_success)) + is DataState.Error -> showSnack(position.message.toString()) + DataState.Loading -> showSnack("Fetching position") } } @@ -205,18 +192,4 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), LocationListener private fun gotoUrl(url: String) { 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)) - } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt deleted file mode 100644 index 19c5e03f..00000000 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settingsScreen/SettingsViewModel.kt +++ /dev/null @@ -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 . - */ -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) { - viewModelScope.launch(coroutineHandler) { -// _satData.value = DataState.Loading - dataRepository.updateDataFromWeb(sources) - } - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/sourcesScreen/SourcesDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/sourcesScreen/SourcesDialog.kt index 3d437630..b6d3e6b2 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/sourcesScreen/SourcesDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/sourcesScreen/SourcesDialog.kt @@ -25,7 +25,7 @@ import android.view.WindowManager import androidx.appcompat.app.AppCompatDialogFragment import androidx.recyclerview.widget.LinearLayoutManager 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.framework.model.Source import com.rtbishop.look4sat.presentation.setNavResult @@ -36,7 +36,7 @@ import javax.inject.Inject class SourcesDialog : AppCompatDialogFragment() { @Inject - lateinit var preferences: PreferencesHandler + lateinit var settings: SettingsHandler override fun onCreateView(inflater: LayoutInflater, group: ViewGroup?, state: Bundle?): View? { return inflater.inflate(R.layout.dialog_sources, group, false) @@ -44,7 +44,7 @@ class SourcesDialog : AppCompatDialogFragment() { override fun onViewCreated(view: View, state: Bundle?) { super.onViewCreated(view, state) - val sources = preferences.loadDataSources() + val sources = settings.loadDataSources() val sourcesAdapter = SourcesAdapter().apply { setSources(sources.map { Source(it) }) } DialogSourcesBinding.bind(view).apply { dialog?.window?.setLayout( diff --git a/build.gradle b/build.gradle index e4f87bc7..b967de6f 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ buildscript { timber_version = '5.0.1' junit_version = '4.13.2' mockito_version = '4.2.0' - leak_canary_version = '2.7' + leak_canary_version = '2.8' } repositories { google() diff --git a/core/src/main/java/com/rtbishop/look4sat/data/DefaultRepository.kt b/core/src/main/java/com/rtbishop/look4sat/data/DefaultRepository.kt index dabc07ed..5f1b3e09 100644 --- a/core/src/main/java/com/rtbishop/look4sat/data/DefaultRepository.kt +++ b/core/src/main/java/com/rtbishop/look4sat/data/DefaultRepository.kt @@ -32,7 +32,7 @@ class DefaultRepository( private val dataParser: DataParser, private val localSource: LocalDataSource, private val remoteSource: RemoteDataSource, - private val preferences: PreferencesHandler + private val settings: SettingsHandler ) : DataRepository { override fun getSatelliteItems() = localSource.getSatelliteItems() @@ -48,7 +48,7 @@ class DefaultRepository( override suspend fun updateDataFromWeb(sources: List) { coroutineScope { launch { - preferences.saveDataSources(sources) + settings.saveDataSources(sources) } launch { val updateTimeMillis = measureTimeMillis { @@ -78,7 +78,7 @@ class DefaultRepository( println("Update from web took $updateTimeMillis ms") } launch { - remoteSource.fetchFileStream(preferences.transmittersSource)?.let { inputStream -> + remoteSource.fetchFileStream(settings.transmittersSource)?.let { inputStream -> val transmitters = dataParser.parseJSONStream(inputStream) localSource.updateTransmitters(transmitters) } diff --git a/core/src/main/java/com/rtbishop/look4sat/data/PreferencesHandler.kt b/core/src/main/java/com/rtbishop/look4sat/data/SettingsHandler.kt similarity index 97% rename from core/src/main/java/com/rtbishop/look4sat/data/PreferencesHandler.kt rename to core/src/main/java/com/rtbishop/look4sat/data/SettingsHandler.kt index 7ca2796c..bbe4bfa5 100644 --- a/core/src/main/java/com/rtbishop/look4sat/data/PreferencesHandler.kt +++ b/core/src/main/java/com/rtbishop/look4sat/data/SettingsHandler.kt @@ -17,7 +17,7 @@ */ package com.rtbishop.look4sat.data -interface PreferencesHandler { +interface SettingsHandler { val defaultSources: List get() = listOf( diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/LocationHandler.kt b/core/src/main/java/com/rtbishop/look4sat/domain/LocationHandler.kt new file mode 100644 index 00000000..70b24710 --- /dev/null +++ b/core/src/main/java/com/rtbishop/look4sat/domain/LocationHandler.kt @@ -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> + + fun getStationPosition(): GeoPos + + fun setStationPosition(latitude: Double, longitude: Double) + + fun setPositionFromLocation() + + fun setPositionFromNet() + + fun setPositionFromGps() + + fun setPositionFromQth(qthString: String) +} diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/LocationProvider.kt b/core/src/main/java/com/rtbishop/look4sat/domain/LocationProvider.kt deleted file mode 100644 index 8ec48454..00000000 --- a/core/src/main/java/com/rtbishop/look4sat/domain/LocationProvider.kt +++ /dev/null @@ -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 -} diff --git a/core/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt b/core/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt index b5263a17..8430df72 100644 --- a/core/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt +++ b/core/src/main/java/com/rtbishop/look4sat/domain/model/DataState.kt @@ -19,7 +19,6 @@ package com.rtbishop.look4sat.domain.model sealed class DataState { data class Success(val data: T) : DataState() - data class Error(val error: Throwable) : DataState() - object Empty : DataState() + data class Error(val message: String?) : DataState() object Loading : DataState() } diff --git a/core/src/test/java/com/rtbishop/look4sat/DataParserTest.kt b/core/src/test/java/com/rtbishop/look4sat/DataParserTest.kt index 4e245cf7..d38ffbfb 100644 --- a/core/src/test/java/com/rtbishop/look4sat/DataParserTest.kt +++ b/core/src/test/java/com/rtbishop/look4sat/DataParserTest.kt @@ -84,8 +84,6 @@ class DataParserTest { fun `Given valid data streams parsed results match`() = runTest(testDispatcher) { val csvResult = dataParser.parseCSVStream(validCSVStream) val tleResult = dataParser.parseTLEStream(validTLEStream) - println(csvResult) - println(tleResult) assert(csvResult == tleResult) }