Fixes and refactoring for SettingsScreen
This commit is contained in:
1 parent
f7f317cf8b
commit
12fabc8f23
16 files changed
+245
-194
No files matched your search
@@ -13,7 +13,7 @@
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -26,12 +26,13 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Look4Sat.SplashScreen">
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Look4Sat.SplashScreen"
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -18,7 +18,6 @@
|
||||
package com.rtbishop.look4sat
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -32,15 +31,13 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent { MainTheme { MainScreen() } }
|
||||
}
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
val newConfig = Configuration(newBase?.resources?.configuration)
|
||||
newConfig.fontScale = 1.0f
|
||||
val newConfig = Configuration(newBase?.resources?.configuration).apply { fontScale = 1.0f }
|
||||
applyOverrideConfiguration(newConfig)
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package com.rtbishop.look4sat.framework
|
||||
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
@@ -35,13 +36,15 @@ class LocationSource(
|
||||
private val settings: ISettingsSource,
|
||||
) : ILocationSource, LocationListenerCompat {
|
||||
|
||||
private val defaultProvider = LocationManager.PASSIVE_PROVIDER
|
||||
private val _stationPosition = MutableStateFlow(settings.loadStationPosition())
|
||||
override val stationPosition: StateFlow<StationPos> = _stationPosition
|
||||
|
||||
override fun setGpsPosition(): Boolean {
|
||||
if (!LocationManagerCompat.isLocationEnabled(manager)) return false
|
||||
forceLocationUpdate(LocationManager.GPS_PROVIDER)
|
||||
forceLocationUpdate(LocationManager.NETWORK_PROVIDER)
|
||||
val criteria = Criteria().apply { isCostAllowed = true }
|
||||
val bestProvider = manager.getBestProvider(criteria, true) ?: defaultProvider
|
||||
forceLocationUpdate(bestProvider)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -65,7 +68,7 @@ class LocationSource(
|
||||
|
||||
private fun forceLocationUpdate(provider: String) {
|
||||
try {
|
||||
val location = manager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
|
||||
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)
|
||||
|
||||
@@ -50,7 +50,7 @@ class SettingsSource(private val prefs: SharedPreferences) : ISettingsSource {
|
||||
override fun loadStationPosition(): StationPos {
|
||||
val latitude = prefs.getString(keyLatitude, null) ?: "0.0"
|
||||
val longitude = prefs.getString(keyLongitude, null) ?: "0.0"
|
||||
val qthLocator = prefs.getString(keyLocator, null) ?: ""
|
||||
val qthLocator = prefs.getString(keyLocator, null) ?: "null"
|
||||
val timestamp = prefs.getLong(keyLocTimestamp, 0L)
|
||||
return StationPos(latitude.toDouble(), longitude.toDouble(), qthLocator, timestamp)
|
||||
}
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ import androidx.compose.foundation.interaction.Interaction
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -28,7 +27,7 @@ fun CardButton(onClick: () -> Unit, text: String, modifier: Modifier = Modifier)
|
||||
onClick = { onClick() }, colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiary,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiary
|
||||
), shape = MaterialTheme.shapes.small, modifier = modifier.padding(start = 4.dp, end = 4.dp)
|
||||
), shape = MaterialTheme.shapes.small, modifier = modifier
|
||||
) { Text(text = text, fontSize = 17.sp) }
|
||||
}
|
||||
|
||||
@@ -60,14 +60,18 @@ private fun MainNavBar(navController: NavController) {
|
||||
|
||||
@Composable
|
||||
private fun MainNavGraph(navController: NavHostController) {
|
||||
val navToPasses = { navController.navigate(Screen.Passes.route) }
|
||||
val navToRadar = { catNum: Int, aosTime: Long ->
|
||||
navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}")
|
||||
}
|
||||
val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}"
|
||||
val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 },
|
||||
navArgument("aosTime") { defaultValue = 0L })
|
||||
NavHost(navController, startDestination = Screen.Passes.route) {
|
||||
composable(Screen.Entries.route) { EntriesScreen(navController) }
|
||||
composable(Screen.Passes.route) { PassesScreen(navController) }
|
||||
composable(radarRoute, radarArgs) { RadarScreen(navController) }
|
||||
composable(Screen.Entries.route) { EntriesScreen(navToPasses) }
|
||||
composable(Screen.Passes.route) { PassesScreen(navToRadar) }
|
||||
composable(radarRoute, radarArgs) { RadarScreen() }
|
||||
composable(Screen.Map.route) { MapScreen() }
|
||||
composable(Screen.Settings.route) { SettingsScreen(navController) }
|
||||
composable(Screen.Settings.route) { SettingsScreen() }
|
||||
}
|
||||
}
|
||||
@@ -26,21 +26,17 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
import com.rtbishop.look4sat.model.SatItem
|
||||
import com.rtbishop.look4sat.presentation.CardLoadingIndicator
|
||||
import com.rtbishop.look4sat.presentation.MainTheme
|
||||
import com.rtbishop.look4sat.presentation.Screen
|
||||
import com.rtbishop.look4sat.presentation.dialogs.TypesDialog
|
||||
import com.rtbishop.look4sat.presentation.onClick
|
||||
|
||||
@Composable
|
||||
fun EntriesScreen(navController: NavController) {
|
||||
val viewModel: EntriesViewModel = hiltViewModel()
|
||||
fun EntriesScreen(navToPasses: () -> Unit, viewModel: EntriesViewModel = hiltViewModel()) {
|
||||
val state = viewModel.satData.collectAsState(initial = DataState.Loading)
|
||||
val navToPasses = { navController.navigate(Screen.Passes.route) }
|
||||
|
||||
val showDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val toggleDialog = { showDialog.value = showDialog.value.not() }
|
||||
|
||||
@@ -102,8 +102,8 @@ private fun setPositions(
|
||||
posMap: Map<Satellite, GeoPos>, mapView: MapView, action: (Satellite) -> Unit
|
||||
) {
|
||||
val markers = FolderOverlay()
|
||||
posMap.entries.forEach {
|
||||
try {
|
||||
try {
|
||||
posMap.entries.forEach {
|
||||
Marker(mapView).apply {
|
||||
setInfoWindow(null)
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||
@@ -119,12 +119,12 @@ private fun setPositions(
|
||||
}
|
||||
markers.add(this)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Timber.d(exception)
|
||||
}
|
||||
mapView.overlays[3] = markers
|
||||
mapView.invalidate()
|
||||
} catch (exception: Exception) {
|
||||
Timber.d(exception)
|
||||
}
|
||||
mapView.overlays[3] = markers
|
||||
mapView.invalidate()
|
||||
}
|
||||
|
||||
private fun getCustomTextIcon(textLabel: String, mapView: MapView): Drawable {
|
||||
@@ -143,33 +143,33 @@ private fun getCustomTextIcon(textLabel: String, mapView: MapView): Drawable {
|
||||
private fun setSatelliteTrack(satTrack: List<List<GeoPos>>, mapView: MapView) {
|
||||
// val center = GeoPoint(satTrack[0][0].lat, satTrack[0][0].lon)
|
||||
val trackOverlay = FolderOverlay()
|
||||
satTrack.forEach { track ->
|
||||
val trackPoints = track.map { GeoPoint(it.lat, it.lon) }
|
||||
Polyline().apply {
|
||||
try {
|
||||
try {
|
||||
satTrack.forEach { track ->
|
||||
val trackPoints = track.map { GeoPoint(it.lat, it.lon) }
|
||||
Polyline().apply {
|
||||
setPoints(trackPoints)
|
||||
outlinePaint.set(trackPaint)
|
||||
trackOverlay.add(this)
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
println(exception.stackTraceToString())
|
||||
}
|
||||
}
|
||||
mapView.overlays[1] = trackOverlay
|
||||
} catch (exception: Exception) {
|
||||
Timber.d(exception)
|
||||
}
|
||||
mapView.overlays[1] = trackOverlay
|
||||
// mapView.controller.animateTo(center)
|
||||
}
|
||||
|
||||
private fun setFootprint(satPos: SatPos, mapView: MapView) {
|
||||
val footprintPoints = satPos.getRangeCircle().map { GeoPoint(it.lat, it.lon) }
|
||||
val footprintOverlay = Polyline().apply {
|
||||
outlinePaint.set(footprintPaint)
|
||||
try {
|
||||
try {
|
||||
val footprintOverlay = Polyline().apply {
|
||||
outlinePaint.set(footprintPaint)
|
||||
setPoints(footprintPoints)
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
println(exception.stackTraceToString())
|
||||
}
|
||||
mapView.overlays[2] = footprintOverlay
|
||||
} catch (exception: Exception) {
|
||||
Timber.d(exception)
|
||||
}
|
||||
mapView.overlays[2] = footprintOverlay
|
||||
}
|
||||
|
||||
//private fun handleMapData(mapData: MapData) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.domain.NearEarthSat
|
||||
import com.rtbishop.look4sat.model.DataState
|
||||
@@ -39,11 +38,7 @@ import java.util.*
|
||||
private val sdf = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
|
||||
|
||||
@Composable
|
||||
fun PassesScreen(navController: NavController) {
|
||||
val viewModel: PassesViewModel = hiltViewModel()
|
||||
val navToRadar = { catNum: Int, aosTime: Long ->
|
||||
navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}")
|
||||
}
|
||||
fun PassesScreen(navToRadar: (Int, Long) -> Unit, viewModel: PassesViewModel = hiltViewModel()) {
|
||||
val state = viewModel.passes.collectAsState(initial = null)
|
||||
val timerText = viewModel.timerText.collectAsState(initial = null)
|
||||
val isRefreshing = state.value is DataState.Loading
|
||||
@@ -185,7 +180,6 @@ private fun Pass(pass: SatPass, navToRadar: (Int, Long) -> Unit, modifier: Modif
|
||||
modifier = modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.padding(top = 3.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.inverseSurface
|
||||
)
|
||||
Text(
|
||||
|
||||
@@ -16,13 +16,11 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.model.SatRadio
|
||||
|
||||
@Composable
|
||||
fun RadarScreen(navController: NavController) {
|
||||
val viewModel: RadarViewModel = hiltViewModel()
|
||||
fun RadarScreen(viewModel: RadarViewModel = hiltViewModel()) {
|
||||
val currentPass = viewModel.getPass().collectAsState(null)
|
||||
|
||||
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
|
||||
@@ -21,15 +21,17 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.rtbishop.look4sat.BuildConfig
|
||||
import com.rtbishop.look4sat.R
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
import com.rtbishop.look4sat.presentation.CardButton
|
||||
import com.rtbishop.look4sat.presentation.MainTheme
|
||||
import com.rtbishop.look4sat.presentation.dialogs.LocatorDialog
|
||||
import com.rtbishop.look4sat.presentation.dialogs.PositionDialog
|
||||
import com.rtbishop.look4sat.presentation.gotoUrl
|
||||
import com.rtbishop.look4sat.presentation.showToast
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
private const val POLICY_URL = "https://sites.google.com/view/look4sat-privacy-policy/home"
|
||||
private const val LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
@@ -38,9 +40,8 @@ private const val DONATE_URL = "https://ko-fi.com/rt_bishop"
|
||||
private const val FDROID_URL = "https://f-droid.org/en/packages/com.rtbishop.look4sat/"
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(navController: NavController) {
|
||||
fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val viewModel: SettingsViewModel = hiltViewModel()
|
||||
|
||||
// Permissions setup
|
||||
val bluetoothContract = ActivityResultContracts.RequestPermission()
|
||||
@@ -76,8 +77,8 @@ fun SettingsScreen(navController: NavController) {
|
||||
val togglePosDialog = { showPosDialog.value = showPosDialog.value.not() }
|
||||
if (showPosDialog.value) {
|
||||
PositionDialog(
|
||||
locSettings.getLatitude,
|
||||
locSettings.getLongitude,
|
||||
locSettings.stationPos.latitude,
|
||||
locSettings.stationPos.longitude,
|
||||
togglePosDialog,
|
||||
locSettings.setManualLoc
|
||||
)
|
||||
@@ -85,7 +86,7 @@ fun SettingsScreen(navController: NavController) {
|
||||
val showLocDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val toggleLocDialog = { showLocDialog.value = showLocDialog.value.not() }
|
||||
if (showLocDialog.value) {
|
||||
LocatorDialog(locSettings.getLocator, toggleLocDialog, locSettings.setQthLoc)
|
||||
LocatorDialog(locSettings.stationPos.qthLocator, toggleLocDialog, locSettings.setQthLoc)
|
||||
}
|
||||
|
||||
// Screen setup
|
||||
@@ -100,11 +101,18 @@ fun SettingsScreen(navController: NavController) {
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CardAboutPreview() = MainTheme { CardAbout(version = "4.0.0") }
|
||||
|
||||
@Composable
|
||||
private fun CardAbout(version: String, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
ElevatedCard(modifier = modifier.fillMaxWidth()) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.Center) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_entries),
|
||||
@@ -128,21 +136,21 @@ private fun CardAbout(version: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.app_subtitle),
|
||||
fontSize = 20.sp,
|
||||
modifier = modifier.padding(top = 4.dp)
|
||||
modifier = modifier.padding(top = 4.dp, bottom = 2.dp)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
CardButton(
|
||||
onClick = { gotoUrl(context, GITHUB_URL) },
|
||||
text = stringResource(id = R.string.btn_github),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = { gotoUrl(context, DONATE_URL) },
|
||||
text = stringResource(id = R.string.btn_donate),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = { gotoUrl(context, FDROID_URL) },
|
||||
text = stringResource(id = R.string.btn_fdroid),
|
||||
@@ -155,8 +163,11 @@ private fun CardAbout(version: String, modifier: Modifier = Modifier) {
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LocationCardPreview() {
|
||||
MainTheme { }
|
||||
private fun LocationCardPreview() = MainTheme {
|
||||
val stationPos = StationPos(0.0, 0.0, "IO91vl", 0L)
|
||||
LocationCard(
|
||||
settings = LocationSettings(true, stationPos, {}, { _, _ -> }, { }),
|
||||
setGpsLoc = {}, togglePosDialog = {}) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -167,36 +178,56 @@ private fun LocationCard(
|
||||
toggleLocDialog: () -> Unit
|
||||
) {
|
||||
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = "Station position")
|
||||
UpdateIndicator(isUpdating = settings.getUpdating, Modifier.weight(1f))
|
||||
Text(
|
||||
text = stringResource(id = R.string.location_title),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
UpdateIndicator(isUpdating = settings.isUpdating, Modifier.weight(1f))
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Updated: ${settings.getLastUpdated}")
|
||||
Text(text = "QTH: ${settings.getLocator}")
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Latitude: ${settings.getLatitude}")
|
||||
Text(text = "Longitude: ${settings.getLongitude}")
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(text = setUpdateTime(updateTime = settings.stationPos.timestamp))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
text = "Lat: ${settings.stationPos.latitude}°",
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.Start
|
||||
)
|
||||
Text(
|
||||
text = "Lon: ${settings.stationPos.longitude}°",
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Text(
|
||||
text = "Qth: ${settings.stationPos.qthLocator}",
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
CardButton(
|
||||
onClick = setGpsLoc, // locationRequest.launch(arrayOf(locationFine, locationCoarse))
|
||||
text = stringResource(id = R.string.btn_gps), modifier = Modifier.weight(1f)
|
||||
onClick = setGpsLoc,
|
||||
text = stringResource(id = R.string.btn_gps),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = togglePosDialog, // open Position dialog
|
||||
text = stringResource(id = R.string.btn_manual), modifier = Modifier.weight(1f)
|
||||
) // viewModel.setStationPosition(position.first, position.second)
|
||||
onClick = togglePosDialog,
|
||||
text = stringResource(id = R.string.btn_manual),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = toggleLocDialog, // open Locator dialog
|
||||
text = stringResource(id = R.string.btn_qth), modifier = Modifier.weight(1f)
|
||||
) // viewModel.setPositionFromQth(locator)
|
||||
onClick = toggleLocDialog,
|
||||
text = stringResource(id = R.string.btn_qth),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,42 +235,52 @@ private fun LocationCard(
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DataCardPreview() {
|
||||
MainTheme { }
|
||||
private fun DataCardPreview() = MainTheme {
|
||||
DataCard(settings = DataSettings(
|
||||
true, 0L, 5000, 2500, {}, { }, {})
|
||||
) {}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DataCard(settings: DataSettings, updateFromFile: () -> Unit) {
|
||||
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = "Satellite data")
|
||||
UpdateIndicator(isUpdating = settings.getUpdating, Modifier.weight(1f))
|
||||
Text(
|
||||
text = stringResource(id = R.string.data_title),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
UpdateIndicator(isUpdating = settings.isUpdating, Modifier.weight(1f))
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Updated: ${settings.getLastUpdated}")
|
||||
Text(text = "")
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Satellites: ${settings.getSatellites}")
|
||||
Text(text = "Transceivers: ${settings.getRadios}")
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(text = setUpdateTime(updateTime = settings.lastUpdated))
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(text = "Satellites: ${settings.satsTotal}")
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(text = "Transceivers: ${settings.radiosTotal}")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
CardButton(
|
||||
onClick = settings.updateFromWeb, // viewModel.updateFromWeb()
|
||||
text = stringResource(id = R.string.btn_web), modifier = Modifier.weight(1f)
|
||||
onClick = settings.updateFromWeb,
|
||||
text = stringResource(id = R.string.btn_web),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = updateFromFile, // contentRequest.launch("*/*")
|
||||
text = stringResource(id = R.string.btn_file), modifier = Modifier.weight(1f)
|
||||
onClick = updateFromFile,
|
||||
text = stringResource(id = R.string.btn_file),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = settings.clearAllData, // viewModel.clearAllData()
|
||||
text = stringResource(id = R.string.btn_clear), modifier = Modifier.weight(1f)
|
||||
onClick = settings.clearAllData,
|
||||
text = stringResource(id = R.string.btn_clear),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -293,15 +334,17 @@ private fun DataCard(settings: DataSettings, updateFromFile: () -> Unit) {
|
||||
//
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun OtherCardPreview() {
|
||||
MainTheme { }
|
||||
private fun OtherCardPreview() = MainTheme {
|
||||
OtherCard(settings = OtherSettings(
|
||||
getUtc = false, getUpdate = true, getSweep = true, getSensor = true,
|
||||
setUtc = {}, setUpdate = {}, setSweep = {}, setSensor = {})
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OtherCard(settings: OtherSettings) {
|
||||
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
Text(text = stringResource(id = R.string.other_title))
|
||||
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -309,7 +352,7 @@ private fun OtherCard(settings: OtherSettings) {
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.other_switch_utc))
|
||||
Switch(checked = settings.getUtc, onCheckedChange = { settings.setUtc(it) })
|
||||
} // viewModel.setUseUTC(isChecked)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -317,7 +360,7 @@ private fun OtherCard(settings: OtherSettings) {
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.other_switch_update))
|
||||
Switch(checked = settings.getUpdate, onCheckedChange = { settings.setUpdate(it) })
|
||||
} // AutoUpdateEnabled(isChecked)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -325,7 +368,7 @@ private fun OtherCard(settings: OtherSettings) {
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.other_switch_sweep))
|
||||
Switch(checked = settings.getSweep, onCheckedChange = { settings.setSweep(it) })
|
||||
} // viewModel.setShowSweep(isChecked)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -333,58 +376,49 @@ private fun OtherCard(settings: OtherSettings) {
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.other_switch_sensors))
|
||||
Switch(checked = settings.getSensor, onCheckedChange = { settings.setSensor(it) })
|
||||
} // viewModel.setUseCompass(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// showToast(getString(R.string.data_clear_success))
|
||||
// showToast(getString(R.string.data_success))
|
||||
//
|
||||
//private fun setUpdateTime(updateTime: Long) {
|
||||
// val updatePattern = getString(R.string.data_update)
|
||||
// val updateDate = if (updateTime == 0L) {
|
||||
// getString(R.string.pass_placeholder)
|
||||
// } else {
|
||||
// SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault()).format(Date(updateTime))
|
||||
// }
|
||||
// binding.settingsData.dataUpdate.text = String.format(updatePattern, updateDate)
|
||||
//}
|
||||
//
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CardCreditsPreview() = MainTheme { CardCredits() }
|
||||
|
||||
@Composable
|
||||
private fun CardCredits(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
ElevatedCard(modifier = modifier.fillMaxWidth()) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.outro_title),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = modifier.padding(8.dp)
|
||||
modifier = modifier.padding(6.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.outro_thanks),
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = modifier.padding(horizontal = 6.dp, vertical = 4.dp)
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.outro_license),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = modifier.padding(8.dp)
|
||||
modifier = modifier.padding(6.dp)
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
CardButton(
|
||||
onClick = { gotoUrl(context, LICENSE_URL) },
|
||||
text = stringResource(id = R.string.btn_license),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
CardButton(
|
||||
onClick = { gotoUrl(context, POLICY_URL) },
|
||||
text = stringResource(id = R.string.btn_privacy),
|
||||
@@ -396,8 +430,26 @@ private fun CardCredits(modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UpdateIndicator(isUpdating: Boolean, modifier: Modifier = Modifier) = if (isUpdating) {
|
||||
LinearProgressIndicator(modifier = modifier.padding(start = 6.dp, end = 6.dp))
|
||||
} else {
|
||||
LinearProgressIndicator(modifier = modifier.padding(start = 6.dp, end = 6.dp), progress = 0f)
|
||||
private fun setUpdateTime(updateTime: Long): String {
|
||||
val updateDate = if (updateTime != 0L) {
|
||||
val timePattern = stringResource(id = R.string.last_updated_pattern)
|
||||
SimpleDateFormat(timePattern, Locale.getDefault()).format(Date(updateTime))
|
||||
} else {
|
||||
stringResource(id = R.string.pass_placeholder)
|
||||
}
|
||||
return stringResource(id = R.string.data_update, updateDate)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UpdateIndicator(isUpdating: Boolean, modifier: Modifier = Modifier) = if (isUpdating) {
|
||||
LinearProgressIndicator(
|
||||
modifier = modifier.padding(start = 6.dp),
|
||||
trackColor = MaterialTheme.colorScheme.inverseSurface
|
||||
)
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
progress = 0f,
|
||||
modifier = modifier.padding(start = 6.dp),
|
||||
trackColor = MaterialTheme.colorScheme.inverseSurface
|
||||
)
|
||||
}
|
||||
+8
-9
@@ -1,21 +1,20 @@
|
||||
package com.rtbishop.look4sat.presentation.settings
|
||||
|
||||
import com.rtbishop.look4sat.model.StationPos
|
||||
|
||||
data class LocationSettings(
|
||||
val getUpdating: Boolean,
|
||||
val getLastUpdated: Long,
|
||||
val getLatitude: Double,
|
||||
val getLongitude: Double,
|
||||
val getLocator: String,
|
||||
val isUpdating: Boolean,
|
||||
val stationPos: StationPos,
|
||||
val setGpsLoc: () -> Unit,
|
||||
val setManualLoc: (Double, Double) -> Unit,
|
||||
val setQthLoc: (String) -> Unit
|
||||
)
|
||||
|
||||
data class DataSettings(
|
||||
val getUpdating: Boolean,
|
||||
val getLastUpdated: Long,
|
||||
val getSatellites: Int,
|
||||
val getRadios: Int,
|
||||
val isUpdating: Boolean,
|
||||
val lastUpdated: Long,
|
||||
val satsTotal: Int,
|
||||
val radiosTotal: Int,
|
||||
val updateFromWeb: () -> Unit,
|
||||
val updateFromFile: (String) -> Unit,
|
||||
val clearAllData: () -> Unit
|
||||
|
||||
@@ -25,6 +25,7 @@ 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
|
||||
@@ -37,11 +38,8 @@ class SettingsViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
private val defaultLocationSettings = LocationSettings(false,
|
||||
locationSource.stationPosition.value.timestamp,
|
||||
locationSource.stationPosition.value.latitude,
|
||||
locationSource.stationPosition.value.longitude,
|
||||
locationSource.stationPosition.value.qthLocator,
|
||||
{ locationSource.setGpsPosition() },
|
||||
locationSource.stationPosition.value,
|
||||
{ updateStationPosition() },
|
||||
{ lat, lon -> locationSource.setGeoPosition(lat, lon) },
|
||||
{ locationSource.setQthPosition(it) })
|
||||
val locationSettings = mutableStateOf(defaultLocationSettings)
|
||||
@@ -66,22 +64,34 @@ class SettingsViewModel @Inject constructor(
|
||||
val otherSettings = mutableStateOf(defaultOtherSettings)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
locationSource.stationPosition.collect { stationPos ->
|
||||
delay(125)
|
||||
locationSettings.value = locationSettings.value.copy(
|
||||
isUpdating = false, stationPos = stationPos
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
repository.updateState.collect {
|
||||
val isUpdating = it is DataState.Loading
|
||||
dataSettings.value = dataSettings.value.copy(
|
||||
getUpdating = isUpdating, getLastUpdated = settings.getLastUpdateTime()
|
||||
isUpdating = isUpdating, lastUpdated = settings.getLastUpdateTime()
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
combine(repository.getEntriesTotal(), repository.getRadiosTotal()) { sats, radios ->
|
||||
dataSettings.value =
|
||||
dataSettings.value.copy(getSatellites = sats, getRadios = radios)
|
||||
dataSettings.value = dataSettings.value.copy(satsTotal = sats, radiosTotal = radios)
|
||||
}.collect {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStationPosition() {
|
||||
val isSuccessful = locationSource.setGpsPosition()
|
||||
locationSettings.value = locationSettings.value.copy(isUpdating = isSuccessful)
|
||||
}
|
||||
|
||||
private fun setUtc(value: Boolean) {
|
||||
settings.setUtcState(value)
|
||||
otherSettings.value = otherSettings.value.copy(getUtc = value)
|
||||
|
||||
@@ -158,24 +158,23 @@
|
||||
<string name="BTremote_output_hint">Формат данных</string>
|
||||
<string name="BTremote_perm_error">Нет разрешения использовать bluetooth</string>
|
||||
|
||||
<string name="other_title">Другие настройки</string>
|
||||
<string name="other_switch_utc">Показывать время по UTC</string>
|
||||
<string name="other_switch_update">Обновлять данные автоматически</string>
|
||||
<string name="other_switch_sweep">Показывать анимацию радара</string>
|
||||
<string name="other_switch_sensors">Использовать сенсоры устройства</string>
|
||||
<string name="other_switch_utc">• Показывать время по UTC</string>
|
||||
<string name="other_switch_update">• Обновлять данные автоматически</string>
|
||||
<string name="other_switch_sweep">• Показывать анимацию радара</string>
|
||||
<string name="other_switch_sensors">• Использовать сенсоры устройства</string>
|
||||
|
||||
<string name="outro_title">Я хотел бы сказать спасибо:</string>
|
||||
<string name="outro_thanks"> • Вам за использование этого приложения! \nЖелаю всегда чистого неба над головой!
|
||||
\n • <a href="https://github.com/g4dpz">David A. B. Johnson</a> и
|
||||
<string name="outro_thanks">• Вам за использование этого приложения! \nЖелаю всегда чистого неба над головой!
|
||||
\n• <a href="https://github.com/g4dpz">David A. B. Johnson</a> и
|
||||
<a href="https://github.com/davidmoten">Dave Moten</a> за работу над predict4java и доступ
|
||||
к ней по <a href="https://gnu.org/licenses/old-licenses/gpl-2.0.en.html">GNU GPLv2</a>
|
||||
\n • <a href="https://github.com/csete">Alexandru Csete</a> за его программу
|
||||
\n• <a href="https://github.com/csete">Alexandru Csete</a> за его программу
|
||||
Gpredict, которая послужила для меня вдохновением.
|
||||
\n • <a href="https://celestrak.com/webmaster.php">Доктору T.S. Kelso</a> за его сайт
|
||||
\n• <a href="https://celestrak.com/webmaster.php">Доктору T.S. Kelso</a> за его сайт
|
||||
<a href="https://celestrak.com/">Celestrak</a> и доступ к файлам орбит Two-Line Element.
|
||||
\n • <a href="https://libre.space/">Libre Space Foundation</a> за проект
|
||||
\n• <a href="https://libre.space/">Libre Space Foundation</a> за проект
|
||||
<a href="https://db.satnogs.org/">SatNOGS</a>, API и базу данных с информацией о спутниках.
|
||||
\n • <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
\n• <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
за интерес к приложению и публикацию интервью на сайте.
|
||||
</string>
|
||||
<string name="outro_license">Это ПО поставляется без гарантий.</string>
|
||||
|
||||
@@ -158,25 +158,24 @@
|
||||
<string name="BTremote_output_hint">数据格式</string>
|
||||
<string name="BTremote_perm_error">请检查蓝牙权限</string>
|
||||
|
||||
<string name="other_title">其他偏好</string>
|
||||
<string name="other_switch_utc">以 UTC 时间显示</string>
|
||||
<string name="other_switch_update">启用卫星数据自动更新</string>
|
||||
<string name="other_switch_sweep">启用雷达扫描动画</string>
|
||||
<string name="other_switch_sensors">使用传感器旋转雷达视图</string>
|
||||
<string name="other_switch_utc">• 以 UTC 时间显示</string>
|
||||
<string name="other_switch_update">• 启用卫星数据自动更新</string>
|
||||
<string name="other_switch_sweep">• 启用雷达扫描动画</string>
|
||||
<string name="other_switch_sensors">• 使用传感器旋转雷达视图</string>
|
||||
|
||||
<string name="outro_title">我要感谢:</string>
|
||||
<string name="outro_thanks">• 感谢下载和使用 Look4Sat!愿你的天空永远晴朗!
|
||||
\n • 简体中文版翻译由 <a href="https://github.com/BA7LWN">BA7LWN</a> 于 2022.5.10 创建。
|
||||
\n • <a href="https://github.com/g4dpz">David A. B. Johnson</a>
|
||||
\n• 简体中文版翻译由 <a href="https://github.com/BA7LWN">BA7LWN</a> 于 2022.5.10 创建。
|
||||
\n• <a href="https://github.com/g4dpz">David A. B. Johnson</a>
|
||||
and <a href="https://github.com/davidmoten">Dave Moten</a> for creating predict4java lib
|
||||
under the <a href="https://gnu.org/licenses/old-licenses/gpl-2.0.en.html">GNU GPLv2</a>.
|
||||
\n • <a href="https://github.com/csete">Alexandru Csete</a> for creating Gpredict
|
||||
\n• <a href="https://github.com/csete">Alexandru Csete</a> for creating Gpredict
|
||||
satellite tracker that inspired the creation of Look4Sat.
|
||||
\n • <a href="https://celestrak.com/webmaster.php">Dr T.S. Kelso</a> for maintaining his
|
||||
\n• <a href="https://celestrak.com/webmaster.php">Dr T.S. Kelso</a> for maintaining his
|
||||
<a href="https://celestrak.com/">Celestrak</a> website that provides access to the TLE data.
|
||||
\n • <a href="https://libre.space/">Libre Space Foundation</a> for their
|
||||
\n• <a href="https://libre.space/">Libre Space Foundation</a> for their
|
||||
<a href="https://db.satnogs.org/">SatNOGS</a> API and DB providing a huge amount of satellite data.
|
||||
\n • <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
\n• <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
team for their interest to the app and the interview published.
|
||||
</string>
|
||||
<string name="outro_license">该应用程序不提供任何保修.</string>
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
<string name="data_success">Update completed successfully</string>
|
||||
<string name="data_clear_success">Data was cleared successfully</string>
|
||||
|
||||
<string name="last_updated_pattern" translatable="false">d MMM yyyy - HH:mm:ss</string>
|
||||
|
||||
<string name="remote_title">Rotator preferences</string>
|
||||
<string name="remote_switch">Enable remote control</string>
|
||||
<string name="remote_ip_hint">IP address</string>
|
||||
@@ -158,24 +160,22 @@
|
||||
<string name="BTremote_output_hint">Data format</string>
|
||||
<string name="BTremote_perm_error">Check your bluetooth permission</string>
|
||||
|
||||
<string name="other_title">Other preferences</string>
|
||||
<string name="other_switch_utc">Show pass time in UTC</string>
|
||||
<string name="other_switch_update">Enable automatic data update</string>
|
||||
<string name="other_switch_sweep">Enable radar sweep animation</string>
|
||||
<string name="other_switch_sensors">Use sensors to rotate radar view</string>
|
||||
<string name="other_switch_utc">• Show pass time in UTC</string>
|
||||
<string name="other_switch_update">• Enable automatic data update</string>
|
||||
<string name="other_switch_sweep">• Enable radar sweep animation</string>
|
||||
<string name="other_switch_sensors">• Use sensors to rotate radar view</string>
|
||||
|
||||
<string name="outro_title">I would like to say thanks to:</string>
|
||||
<string name="outro_thanks">• You for downloading and using Look4Sat! \nMay your sky always be clear!
|
||||
\n\n• <a href="https://github.com/g4dpz">David A. B. Johnson</a>
|
||||
and <a href="https://github.com/davidmoten">Dave Moten</a> for creating predict4java lib
|
||||
under the <a href="https://gnu.org/licenses/old-licenses/gpl-2.0.en.html">GNU GPLv2</a>.
|
||||
\n\n• <a href="https://github.com/csete">Alexandru Csete</a> for creating Gpredict
|
||||
satellite tracker that inspired the creation of Look4Sat.
|
||||
\n\n• <a href="https://celestrak.com/webmaster.php">Dr T.S. Kelso</a> for maintaining his
|
||||
\n• <a href="https://github.com/g4dpz">David A. B. Johnson</a>
|
||||
and <a href="https://github.com/davidmoten">Dave Moten</a> for creating a super useful predict4java library.
|
||||
\n• <a href="https://github.com/csete">Alexandru Csete</a> for creating Gpredict
|
||||
tracker that inspired the creation of Look4Sat.
|
||||
\n• <a href="https://celestrak.com/webmaster.php">Dr T.S. Kelso</a> for maintaining his
|
||||
<a href="https://celestrak.com/">Celestrak</a> website that provides access to the TLE data.
|
||||
\n\n• <a href="https://libre.space/">Libre Space Foundation</a> for their
|
||||
\n• <a href="https://libre.space/">Libre Space Foundation</a> for their
|
||||
<a href="https://db.satnogs.org/">SatNOGS</a> API and DB providing a huge amount of data.
|
||||
\n\n• <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
\n• <a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
team for their interest to the app and the interview published.
|
||||
</string>
|
||||
<string name="outro_license">The app comes with no warranty.</string>
|
||||
|
||||
Reference in new issue
Block a user