mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Fixes and refactoring for LocationSource
This commit is contained in:
1 parent
12fabc8f23
commit
542cfcfd50
19 files changed
+125
-95
No files matched your search
@@ -18,73 +18,67 @@
|
||||
package com.rtbishop.look4sat.framework
|
||||
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.os.CancellationSignal
|
||||
import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.utility.QthConverter
|
||||
import com.rtbishop.look4sat.utility.round
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class LocationSource(
|
||||
private val manager: LocationManager,
|
||||
private val settings: ISettingsSource,
|
||||
) : ILocationSource, LocationListenerCompat {
|
||||
) : ILocationSource {
|
||||
|
||||
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
|
||||
private val _stationPosition = MutableStateFlow(settings.loadStationPosition())
|
||||
override val stationPosition: StateFlow<StationPos> = _stationPosition
|
||||
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private val timeoutSignal = CancellationSignal().apply {
|
||||
setOnCancelListener { _stationPosition.value = _stationPosition.value }
|
||||
}
|
||||
override val stationPosition: StateFlow<GeoPos> = _stationPosition
|
||||
|
||||
override fun setGpsPosition(): Boolean {
|
||||
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
|
||||
val criteria = Criteria().apply { isCostAllowed = true }
|
||||
val bestProvider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
||||
forceLocationUpdate(bestProvider)
|
||||
try {
|
||||
val criteria = Criteria().apply { isCostAllowed = true }
|
||||
val provider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
||||
Timber.d("Requesting location for $provider provider")
|
||||
LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) {
|
||||
it?.let { setGeoPosition(it.latitude, it.longitude, it.altitude) }
|
||||
}
|
||||
} catch (exception: SecurityException) {
|
||||
Timber.d("No permissions were given")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setGeoPosition(latitude: Double, longitude: Double): Boolean {
|
||||
manager.removeUpdates(this)
|
||||
override fun setGeoPosition(latitude: Double, longitude: Double, altitude: Double): Boolean {
|
||||
val newLongitude = if (longitude > 180.0) longitude - 180 else longitude
|
||||
val locator = QthConverter.positionToQth(latitude, newLongitude) ?: return false
|
||||
saveStationPosition(latitude, newLongitude, locator)
|
||||
saveGeoPos(latitude, newLongitude, altitude, locator)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setQthPosition(locator: String): Boolean {
|
||||
val position = QthConverter.qthToPosition(locator) ?: return false
|
||||
saveStationPosition(position.lat, position.lon, locator)
|
||||
saveGeoPos(position.latitude, position.longitude, 0.0, locator)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location) {
|
||||
setGeoPosition(location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
private fun forceLocationUpdate(provider: String) {
|
||||
try {
|
||||
val location = manager.getLastKnownLocation(defaultProvider)
|
||||
if (location == null || System.currentTimeMillis() - location.time > 600000) {
|
||||
Timber.d("Requesting $provider location update")
|
||||
manager.requestLocationUpdates(provider, 0L, 0f, this)
|
||||
} else {
|
||||
setGeoPosition(location.latitude, location.longitude)
|
||||
}
|
||||
} catch (exception: SecurityException) {
|
||||
Timber.d("No permissions were given")
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveStationPosition(latitude: Double, longitude: Double, locator: String) {
|
||||
private fun saveGeoPos(latitude: Double, longitude: Double, altitude: Double, locator: String) {
|
||||
val newLat = latitude.round(4)
|
||||
val newLon = longitude.round(4)
|
||||
Timber.d("Received new Position($newLat, $newLon) & Locator $locator")
|
||||
_stationPosition.value = StationPos(newLat, newLon, locator, System.currentTimeMillis())
|
||||
val newAlt = altitude.round(1)
|
||||
val timestamp = System.currentTimeMillis()
|
||||
Timber.d("Received new Position($newLat, $newLon, $newAlt) & Locator $locator")
|
||||
_stationPosition.value = GeoPos(newLat, newLon, newAlt, locator, timestamp)
|
||||
settings.saveStationPosition(stationPosition.value)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,10 @@ package com.rtbishop.look4sat.framework
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
|
||||
class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
|
||||
|
||||
@@ -42,22 +45,25 @@ class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
|
||||
const val keyBTFormat = "BTFormat"
|
||||
const val keyLatitude = "stationLat"
|
||||
const val keyLongitude = "stationLon"
|
||||
const val keyAltitude = "stationAlt"
|
||||
const val keyLocator = "stationQTH"
|
||||
const val keyLocTimestamp = "locTimestamp"
|
||||
const val keySelection = "selection"
|
||||
}
|
||||
|
||||
override fun loadStationPosition(): StationPos {
|
||||
val latitude = prefs.getString(keyLatitude, null) ?: "0.0"
|
||||
val longitude = prefs.getString(keyLongitude, null) ?: "0.0"
|
||||
override fun loadStationPosition(): GeoPos {
|
||||
val latitude = (prefs.getString(keyLatitude, null) ?: "0.0").toDouble()
|
||||
val longitude = (prefs.getString(keyLongitude, null) ?: "0.0").toDouble()
|
||||
val altitude = (prefs.getString(keyAltitude, null) ?: "0.0").toDouble()
|
||||
val qthLocator = prefs.getString(keyLocator, null) ?: "null"
|
||||
val timestamp = prefs.getLong(keyLocTimestamp, 0L)
|
||||
return StationPos(latitude.toDouble(), longitude.toDouble(), qthLocator, timestamp)
|
||||
return GeoPos(latitude, longitude, altitude, qthLocator, timestamp)
|
||||
}
|
||||
|
||||
override fun saveStationPosition(stationPos: StationPos) = prefs.edit {
|
||||
override fun saveStationPosition(stationPos: GeoPos) = prefs.edit {
|
||||
putString(keyLatitude, stationPos.latitude.toString())
|
||||
putString(keyLongitude, stationPos.longitude.toString())
|
||||
putString(keyAltitude, stationPos.altitude.toString())
|
||||
putString(keyLocator, stationPos.qthLocator)
|
||||
putLong(keyLocTimestamp, stationPos.timestamp)
|
||||
}
|
||||
@@ -209,4 +215,24 @@ class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
|
||||
private fun SharedPreferences.Editor.putDouble(key: String, double: Double) {
|
||||
putLong(key, double.toRawBits())
|
||||
}
|
||||
|
||||
inline fun <reified T> SharedPreferences.observeKey(key: String, default: T): Flow<T> {
|
||||
val flow = MutableStateFlow(getItem(key, default))
|
||||
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, k ->
|
||||
if (key == k) flow.value = getItem(key, default)
|
||||
}
|
||||
registerOnSharedPreferenceChangeListener(listener)
|
||||
return flow.onCompletion { unregisterOnSharedPreferenceChangeListener(listener) }
|
||||
}
|
||||
|
||||
inline fun <reified T> SharedPreferences.getItem(key: String, default: T): T {
|
||||
return when (default) {
|
||||
is Boolean -> getBoolean(key, default) as T
|
||||
is Int -> getInt(key, default) as T
|
||||
is Long -> getLong(key, default) as T
|
||||
is Float -> getFloat(key, default) as T
|
||||
is String -> getString(key, default) as T
|
||||
else -> throw IllegalArgumentException("Could not handle ${T::class.java.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,6 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
import androidx.compose.material.icons.outlined.PlayArrow
|
||||
import androidx.compose.material.icons.outlined.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -96,7 +92,8 @@ private fun TopBar(setQuery: (String) -> Unit, saveSelection: () -> Unit) {
|
||||
@Composable
|
||||
private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) {
|
||||
val currentQuery = rememberSaveable { mutableStateOf("") }
|
||||
OutlinedTextField(value = currentQuery.value,
|
||||
OutlinedTextField(
|
||||
value = currentQuery.value,
|
||||
onValueChange = { newValue ->
|
||||
currentQuery.value = newValue
|
||||
setQuery(newValue)
|
||||
@@ -104,9 +101,14 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier)
|
||||
maxLines = 1,
|
||||
// label = { Text(text = stringResource(id = R.string.entries_search_hint)) },
|
||||
placeholder = { Text(text = stringResource(id = R.string.entries_search_hint)) },
|
||||
leadingIcon = { Icon(imageVector = Icons.Outlined.Search, contentDescription = null) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_search),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Icon(imageVector = Icons.Outlined.Close,
|
||||
Icon(painter = painterResource(id = R.drawable.ic_close),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.onClick {
|
||||
currentQuery.value = ""
|
||||
@@ -150,7 +152,7 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier
|
||||
.height(48.dp)
|
||||
.clickable { onClick() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.PlayArrow,
|
||||
painter = painterResource(id = R.drawable.ic_next),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
|
||||
@@ -89,7 +89,7 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
|
||||
setInfoWindow(null)
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)
|
||||
position = GeoPoint(stationPos.lat, stationPos.lon)
|
||||
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
||||
mapView.overlays[0] = this
|
||||
mapView.invalidate()
|
||||
}
|
||||
@@ -109,7 +109,7 @@ private fun setPositions(
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
icon = getCustomTextIcon(it.key.data.name, mapView)
|
||||
try {
|
||||
position = GeoPoint(it.value.lat, it.value.lon)
|
||||
position = GeoPoint(it.value.latitude, it.value.longitude)
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
println(exception.stackTraceToString())
|
||||
}
|
||||
@@ -145,7 +145,7 @@ private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
|
||||
val trackOverlay = FolderOverlay()
|
||||
try {
|
||||
satTrack.forEach { track ->
|
||||
val trackPoints = track.map { GeoPoint(it.lat, it.lon) }
|
||||
val trackPoints = track.map { GeoPoint(it.latitude, it.longitude) }
|
||||
Polyline().apply {
|
||||
setPoints(trackPoints)
|
||||
outlinePaint.set(trackPaint)
|
||||
@@ -160,7 +160,7 @@ private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
|
||||
}
|
||||
|
||||
private fun setFootprint(satPos: SatPos, mapView: MapView) {
|
||||
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) }
|
||||
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.latitude, it.longitude) }
|
||||
try {
|
||||
val footprintOverlay = Polyline().apply {
|
||||
outlinePaint.set(footprintPaint)
|
||||
|
||||
@@ -53,8 +53,8 @@ class MapViewModel @Inject constructor(
|
||||
private var selectedSatellite: Satellite? = null
|
||||
|
||||
val stationPos = flow {
|
||||
val osmLat = clipLat(stationPosition.lat)
|
||||
val osmLon = clipLon(stationPosition.lon)
|
||||
val osmLat = clipLat(stationPosition.latitude)
|
||||
val osmLon = clipLon(stationPosition.longitude)
|
||||
emit(GeoPos(osmLat, osmLon))
|
||||
}
|
||||
|
||||
@@ -127,20 +127,20 @@ class MapViewModel @Inject constructor(
|
||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||
val currentPosition = GeoPos(osmLat, osmLon)
|
||||
if (oldLongitude < -170.0 && currentPosition.lon > 170.0) {
|
||||
if (oldLongitude < -170.0 && currentPosition.longitude > 170.0) {
|
||||
// adding left terminal position
|
||||
currentTrack.add(GeoPos(osmLat, -180.0))
|
||||
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
||||
satTracks.add(finishedTrack)
|
||||
currentTrack.clear()
|
||||
} else if (oldLongitude > 170.0 && currentPosition.lon < -170.0) {
|
||||
} else if (oldLongitude > 170.0 && currentPosition.longitude < -170.0) {
|
||||
// adding right terminal position
|
||||
currentTrack.add(GeoPos(osmLat, 180.0))
|
||||
val finishedTrack = mutableListOf<GeoPos>().apply { addAll(currentTrack) }
|
||||
satTracks.add(finishedTrack)
|
||||
currentTrack.clear()
|
||||
}
|
||||
oldLongitude = currentPosition.lon
|
||||
oldLongitude = currentPosition.longitude
|
||||
currentTrack.add(currentPosition)
|
||||
}
|
||||
satTracks.add(currentTrack)
|
||||
@@ -183,7 +183,7 @@ class MapViewModel @Inject constructor(
|
||||
val osmLat = clipLat(satPos.latitude.toDegrees())
|
||||
val osmLon = clipLon(satPos.longitude.toDegrees())
|
||||
val osmPos = GeoPos(osmLat, osmLon)
|
||||
val qthLoc = QthConverter.positionToQth(osmPos.lat, osmPos.lon) ?: "-- --"
|
||||
val qthLoc = QthConverter.positionToQth(osmPos.latitude, osmPos.longitude) ?: "-- --"
|
||||
val velocity = satPos.getOrbitalVelocity()
|
||||
val phase = satPos.phase.toDegrees()
|
||||
val visibility = satPos.eclipsed
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -94,8 +95,8 @@ private fun TransmitterItem(radio: SatRadio) {
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_prev),
|
||||
contentDescription = null
|
||||
painter = painterResource(id = R.drawable.ic_next),
|
||||
contentDescription = null, modifier = Modifier.rotate(180f)
|
||||
)
|
||||
Text(
|
||||
text = radio.info,
|
||||
|
||||
@@ -52,7 +52,7 @@ class RadarViewModel @Inject constructor(
|
||||
private val locationSource: ILocationSource
|
||||
) : ViewModel() {
|
||||
|
||||
private val stationPos = locationSource.stationPosition.value.toGeoPos()
|
||||
private val stationPos = locationSource.stationPosition.value
|
||||
private val magDeclination = getMagDeclination(stationPos)
|
||||
val transmitters = mutableStateOf<List<SatRadio>>(emptyList())
|
||||
val radarData = mutableStateOf<RadarData?>(null)
|
||||
@@ -102,8 +102,8 @@ class RadarViewModel @Inject constructor(
|
||||
fun getShowSweep(): Boolean = settings.isSweepEnabled()
|
||||
|
||||
private fun getMagDeclination(geoPos: GeoPos, time: Long = System.currentTimeMillis()): Float {
|
||||
val latitude = geoPos.lat.toFloat()
|
||||
val longitude = geoPos.lon.toFloat()
|
||||
val latitude = geoPos.latitude.toFloat()
|
||||
val longitude = geoPos.longitude.toFloat()
|
||||
return GeomagneticField(latitude, longitude, 0f, time).declination
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.rtbishop.look4sat.BuildConfig
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
import com.rtbishop.look4sat.presentation.CardButton
|
||||
import com.rtbishop.look4sat.presentation.MainTheme
|
||||
import com.rtbishop.look4sat.presentation.dialogs.LocatorDialog
|
||||
@@ -164,7 +164,7 @@ private fun CardAbout(version: String, modifier: Modifier = Modifier) {
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LocationCardPreview() = MainTheme {
|
||||
val stationPos = StationPos(0.0, 0.0, "IO91vl", 0L)
|
||||
val stationPos = GeoPos(0.0, 0.0, 0.0, "IO91vl", 0L)
|
||||
LocationCard(
|
||||
settings = LocationSettings(true, stationPos, {}, { _, _ -> }, { }),
|
||||
setGpsLoc = {}, togglePosDialog = {}) {}
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package com.rtbishop.look4sat.presentation.settings
|
||||
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
import com.rtbishop.look4sat.model.GeoPos
|
||||
|
||||
data class LocationSettings(
|
||||
val isUpdating: Boolean,
|
||||
val stationPos: StationPos,
|
||||
val stationPos: GeoPos,
|
||||
val setGpsLoc: () -> Unit,
|
||||
val setManualLoc: (Double, Double) -> Unit,
|
||||
val setQthLoc: (String) -> Unit
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.rtbishop.look4sat.domain.ILocationSource
|
||||
import com.rtbishop.look4sat.domain.ISettingsSource
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
@@ -66,7 +65,6 @@ class SettingsViewModel @Inject constructor(
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
locationSource.stationPosition.collect { stationPos ->
|
||||
delay(125)
|
||||
locationSettings.value = locationSettings.value.copy(
|
||||
isUpdating = false, stationPos = stationPos
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,6v14l-11,-7z" />
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
||||
@@ -6,5 +6,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,6v14l11,-7z" />
|
||||
android:pathData="M10,8.64L15.27,12 10,15.36V8.64M8,5v14l11,-7L8,5z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||
</vector>
|
||||
Reference in new issue
Block a user