Fixes and refactoring for SettingsScreen

This commit is contained in:
Arty Bishop committed 2025-12-27 15:01:33 +00:00
1 parent f7f317cf8b
commit 12fabc8f23
16 files changed
+245 -194

No files matched your search

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