fix(roaming): drop auto GPS polling, show station position directly

Reverts the auto-update machinery after review — the page now simply mirrors the station position (站位) from the shared settingsRepo.stationPosition StateFlow, exactly what the Settings page shows:

- RoamingScreen: removed the LocationManager listeners, the 30s re-request loop, the provider filter and the location-disabled hint. No polling, no auto-updates; coordinates are whatever the station GPS says.
- Settings: removed the '漫游位置实时更新' toggle (stateOfRoamingLive, key, action, strings en/zh/tr) that caused the 'Other' card to overflow — the sixth unlabeled switch clipped past the card's rounded bottom edge was that row overflowing a fixed-height card. Card height back to 268.dp, five rows fit again.

Verified: core:domain tests, roaming/settings/app compile clean; zero references to RoamingLive remain.
This commit is contained in:
mckero committed 2026-08-03 12:24:07 +00:00
1 parent 44c442ddd3
commit 97c6d609ac
9 files changed
+10 -121

No files matched your search

@@ -76,7 +76,6 @@ class SettingsRepo(
private val keyStateOfUtc = "stateOfUtc" private val keyStateOfUtc = "stateOfUtc"
private val keyStateOfLightTheme = "stateOfLightTheme" private val keyStateOfLightTheme = "stateOfLightTheme"
private val keyStateOfNightMode = "stateOfNightMode" private val keyStateOfNightMode = "stateOfNightMode"
private val keyStateOfRoamingLive = "stateOfRoamingLive"
private val keyStationAltitude = "stationAltitude" private val keyStationAltitude = "stationAltitude"
private val keyStationLatitude = "stationLatitude" private val keyStationLatitude = "stationLatitude"
private val keyStationLongitude = "stationLongitude" private val keyStationLongitude = "stationLongitude"
@@ -352,7 +351,6 @@ class SettingsRepo(
putBoolean(keyStateOfUtc, new.stateOfUtc) putBoolean(keyStateOfUtc, new.stateOfUtc)
putBoolean(keyStateOfLightTheme, new.stateOfLightTheme) putBoolean(keyStateOfLightTheme, new.stateOfLightTheme)
putBoolean(keyStateOfNightMode, new.stateOfNightMode) putBoolean(keyStateOfNightMode, new.stateOfNightMode)
putBoolean(keyStateOfRoamingLive, new.stateOfRoamingLive)
putBoolean(keyShouldSeeWarning, new.shouldSeeWarning) putBoolean(keyShouldSeeWarning, new.shouldSeeWarning)
putBoolean(keyShouldSeeWhatsNew, new.shouldSeeWhatsNew) putBoolean(keyShouldSeeWhatsNew, new.shouldSeeWhatsNew)
putString(keySstvMode, new.sstvMode) putString(keySstvMode, new.sstvMode)
@@ -370,7 +368,6 @@ class SettingsRepo(
stateOfUtc = preferences.getBoolean(keyStateOfUtc, false), stateOfUtc = preferences.getBoolean(keyStateOfUtc, false),
stateOfLightTheme = preferences.getBoolean(keyStateOfLightTheme, false), stateOfLightTheme = preferences.getBoolean(keyStateOfLightTheme, false),
stateOfNightMode = preferences.getBoolean(keyStateOfNightMode, false), stateOfNightMode = preferences.getBoolean(keyStateOfNightMode, false),
stateOfRoamingLive = preferences.getBoolean(keyStateOfRoamingLive, true),
shouldSeeWarning = preferences.getBoolean(keyShouldSeeWarning, true), shouldSeeWarning = preferences.getBoolean(keyShouldSeeWarning, true),
shouldSeeWhatsNew = preferences.getBoolean(keyShouldSeeWhatsNew, true), shouldSeeWhatsNew = preferences.getBoolean(keyShouldSeeWhatsNew, true),
sstvMode = preferences.getString(keySstvMode, null) ?: "Auto", sstvMode = preferences.getString(keySstvMode, null) ?: "Auto",
@@ -58,7 +58,6 @@ data class OtherSettings(
val stateOfUtc: Boolean, val stateOfUtc: Boolean,
val stateOfLightTheme: Boolean, val stateOfLightTheme: Boolean,
val stateOfNightMode: Boolean = false, val stateOfNightMode: Boolean = false,
val stateOfRoamingLive: Boolean = true,
val shouldSeeWarning: Boolean, val shouldSeeWarning: Boolean,
val shouldSeeWhatsNew: Boolean, val shouldSeeWhatsNew: Boolean,
val sstvMode: String = "Auto", val sstvMode: String = "Auto",
@@ -186,7 +186,6 @@
<string name="prefs_other_switch_sweep">Radar taramasını etkinleştir</string> <string name="prefs_other_switch_sweep">Radar taramasını etkinleştir</string>
<string name="prefs_other_switch_sensors">Radar görünümünü döndürmek için sensörleri kullan</string> <string name="prefs_other_switch_sensors">Radar görünümünü döndürmek için sensörleri kullan</string>
<string name="prefs_other_switch_night_mode">Kırmızı gece filtresini etkinleştir</string> <string name="prefs_other_switch_night_mode">Kırmızı gece filtresini etkinleştir</string>
<string name="prefs_other_switch_roaming_live">Roaming konumunu canlı güncelle</string>
<string name="prefs_highlight_title">Elevasyon vurgusu</string> <string name="prefs_highlight_title">Elevasyon vurgusu</string>
<string name="prefs_highlight_low">Düşük &lt; %1$d°</string> <string name="prefs_highlight_low">Düşük &lt; %1$d°</string>
@@ -162,7 +162,6 @@
<string name="prefs_other_switch_sweep">启用雷达扫描动画</string> <string name="prefs_other_switch_sweep">启用雷达扫描动画</string>
<string name="prefs_other_switch_sensors">使用传感器旋转雷达视图</string> <string name="prefs_other_switch_sensors">使用传感器旋转雷达视图</string>
<string name="prefs_other_switch_night_mode">启用红色夜间模式</string> <string name="prefs_other_switch_night_mode">启用红色夜间模式</string>
<string name="prefs_other_switch_roaming_live">漫游位置实时更新</string>
<string name="prefs_highlight_title">仰角高亮</string> <string name="prefs_highlight_title">仰角高亮</string>
<string name="prefs_highlight_low">低 &lt; %1$d°</string> <string name="prefs_highlight_low">低 &lt; %1$d°</string>
@@ -197,7 +197,6 @@
<string name="prefs_other_switch_sweep">Enable radar sweep animation</string> <string name="prefs_other_switch_sweep">Enable radar sweep animation</string>
<string name="prefs_other_switch_sensors">Use sensors to rotate radar view</string> <string name="prefs_other_switch_sensors">Use sensors to rotate radar view</string>
<string name="prefs_other_switch_night_mode">Enable red night mode filter</string> <string name="prefs_other_switch_night_mode">Enable red night mode filter</string>
<string name="prefs_other_switch_roaming_live">Update Roaming position live</string>
<string name="prefs_highlight_title">Elevation highlight</string> <string name="prefs_highlight_title">Elevation highlight</string>
<string name="prefs_highlight_low">Low &lt; %1$d°</string> <string name="prefs_highlight_low">Low &lt; %1$d°</string>
@@ -17,18 +17,7 @@
*/ */
package com.rtbishop.look4sat.feature.roaming package com.rtbishop.look4sat.feature.roaming
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
@@ -47,12 +36,8 @@ import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -63,12 +48,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign 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.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.domain.utility.positionToQth
import com.rtbishop.look4sat.core.domain.utility.qthNeighbors
import com.rtbishop.look4sat.core.domain.utility.qthToSquare
import com.rtbishop.look4sat.core.presentation.LocalSpacing import com.rtbishop.look4sat.core.presentation.LocalSpacing
import com.rtbishop.look4sat.core.presentation.ScreenColumn import com.rtbishop.look4sat.core.presentation.ScreenColumn
import com.rtbishop.look4sat.core.presentation.TopBar import com.rtbishop.look4sat.core.presentation.TopBar
@@ -83,78 +64,21 @@ import java.util.Locale
* and row are wider/taller, matching the reference app). Styled with the * and row are wider/taller, matching the reference app). Styled with the
* Look4Sat Material 3 theme. * Look4Sat Material 3 theme.
* *
* Live GPS: while the screen is shown (and the "漫游位置实时更新" setting is on) * Coordinates are read straight from the shared settingsRepo.stationPosition
* a LocationManager listener requests gps+network fixes every 10s / 10m, * StateFlow — the same source the Settings page uses — so the page shows
* exactly like the reference app; each fix is pushed through the shared * exactly the station position (站位), whatever the GPS says there. No
* settingsRepo.stationPosition StateFlow, so the Settings page and every * automatic location polling on this page.
* other consumer update in lockstep. The red marker is placed at the
* fractional position derived from the 3rd character pair of the locator
* (ported from the QTH定位器 app), scaled to the actual cell size, so it
* stays accurate on any screen (phones, tablets, wide/narrow).
*/ */
@SuppressLint("MissingPermission")
@Composable @Composable
fun RoamingScreen() { fun RoamingScreen() {
val context = LocalContext.current val context = LocalContext.current
val container = (context.applicationContext as IContainerProvider).getMainContainer() val container = (context.applicationContext as IContainerProvider).getMainContainer()
val settingsRepo = container.settingsRepo val stationPos by container.settingsRepo.stationPosition.collectAsStateWithLifecycle()
val stationPos by settingsRepo.stationPosition.collectAsStateWithLifecycle()
val otherSettings by settingsRepo.otherSettings.collectAsStateWithLifecycle()
val state = remember(stationPos, otherSettings.stateOfRoamingLive) { val state = remember(stationPos) {
RoamingState.fromPosition(stationPos) RoamingState.fromPosition(stationPos)
} }
// Real-time location updates, ported from the reference app's onResume/onPause:
// register gps+network listeners while visible, remove them when leaving.
var gpsAvailable by remember { mutableStateOf(false) }
DisposableEffect(Unit) {
val locationManager = context.getSystemService(LocationManager::class.java)
val hasPermission = ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
gpsAvailable = hasPermission && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val listener = object : LocationListener {
override fun onLocationChanged(location: Location) {
// Accept only real gps/network fixes, like the reference app
if (location.provider != LocationManager.GPS_PROVIDER &&
location.provider != LocationManager.NETWORK_PROVIDER
) return
settingsRepo.setStationPosition(location.latitude, location.longitude, location.altitude)
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) = Unit
override fun onProviderEnabled(provider: String) {
if (provider == LocationManager.GPS_PROVIDER) gpsAvailable = true
}
override fun onProviderDisabled(provider: String) {
if (provider == LocationManager.GPS_PROVIDER) gpsAvailable = false
}
}
if (hasPermission) {
// Listen continuously while the page is visible (10s / 10m, like the reference)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 10_000L, 10f, listener
)
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 10_000L, 10f, listener
)
}
onDispose {
if (hasPermission) locationManager.removeUpdates(listener)
}
}
// Periodic refresh when live updates are enabled: re-request a fix every 30s
// so the page also recovers after the GPS chip falls idle.
LaunchedEffect(otherSettings.stateOfRoamingLive) {
while (otherSettings.stateOfRoamingLive) {
settingsRepo.setStationPosition()
kotlinx.coroutines.delay(30_000L)
}
}
ScreenColumn( ScreenColumn(
topBar = { topBar = {
TopBar { Text(text = "漫游", fontSize = 20.sp, fontWeight = FontWeight.Bold) } TopBar { Text(text = "漫游", fontSize = 20.sp, fontWeight = FontWeight.Bold) }
@@ -165,16 +89,15 @@ fun RoamingScreen() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(spacing.medium) verticalArrangement = Arrangement.spacedBy(spacing.medium)
) { ) {
InfoCard(state = state, gpsAvailable = gpsAvailable) InfoCard(state = state)
GridCard(state = state, modifier = Modifier.weight(1f)) GridCard(state = state, modifier = Modifier.weight(1f))
} }
} }
} }
@Composable @Composable
private fun InfoCard(state: RoamingState, gpsAvailable: Boolean) { private fun InfoCard(state: RoamingState) {
val colorScheme = MaterialTheme.colorScheme val colorScheme = MaterialTheme.colorScheme
val context = LocalContext.current
ElevatedCard(modifier = Modifier.fillMaxWidth()) { ElevatedCard(modifier = Modifier.fillMaxWidth()) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -195,13 +118,7 @@ private fun InfoCard(state: RoamingState, gpsAvailable: Boolean) {
modifier = Modifier modifier = Modifier
.size(10.dp) .size(10.dp)
.clip(CircleShape) .clip(CircleShape)
.background( .background(if (state.gpsEnabled) colorScheme.primary else colorScheme.outline)
when {
state.gpsEnabled -> colorScheme.primary
gpsAvailable -> colorScheme.error
else -> colorScheme.outline
}
)
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text( Text(
@@ -217,22 +134,6 @@ private fun InfoCard(state: RoamingState, gpsAvailable: Boolean) {
color = colorScheme.onSurface color = colorScheme.onSurface
) )
} }
// GPS disabled hint, ported from the reference app's btnLocationSettings:
// opens the system location settings so the user can re-enable GPS.
if (!gpsAvailable) {
Text(
text = "定位未开启,点击前往系统设置",
fontSize = 13.sp,
color = colorScheme.error,
modifier = Modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.small)
.clickable {
context.startActivity(Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
.padding(vertical = 6.dp)
)
}
// Coordinates: DMS + decimal // Coordinates: DMS + decimal
CoordinateRow(label = "纬度", value = state.latitude, isLat = true) CoordinateRow(label = "纬度", value = state.latitude, isLat = true)
CoordinateRow(label = "经度", value = state.longitude, isLat = false) CoordinateRow(label = "经度", value = state.longitude, isLat = false)
@@ -488,7 +488,7 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
ElevatedCard( ElevatedCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(296.dp) .height(268.dp)
) { ) {
Column( Column(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
@@ -513,9 +513,6 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
SwitchRow(R.string.prefs_other_switch_night_mode, settings.stateOfNightMode) { SwitchRow(R.string.prefs_other_switch_night_mode, settings.stateOfNightMode) {
onAction(SettingsAction.ToggleNightMode(it)) onAction(SettingsAction.ToggleNightMode(it))
} }
SwitchRow(R.string.prefs_other_switch_roaming_live, settings.stateOfRoamingLive) {
onAction(SettingsAction.ToggleRoamingLive(it))
}
} }
} }
} }
@@ -64,7 +64,6 @@ sealed interface SettingsAction {
data class ToggleSensor(val value: Boolean) : SettingsAction data class ToggleSensor(val value: Boolean) : SettingsAction
data class ToggleLightTheme(val value: Boolean) : SettingsAction data class ToggleLightTheme(val value: Boolean) : SettingsAction
data class ToggleNightMode(val value: Boolean) : SettingsAction data class ToggleNightMode(val value: Boolean) : SettingsAction
data class ToggleRoamingLive(val value: Boolean) : SettingsAction
// Remote control // Remote control
data class UpdateRC(val settings: RCSettings) : SettingsAction data class UpdateRC(val settings: RCSettings) : SettingsAction
@@ -121,7 +121,6 @@ class SettingsViewModel(
is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) } is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) }
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) } is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) }
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) } is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
is SettingsAction.ToggleRoamingLive -> settingsRepo.updateOtherSettings { it.copy(stateOfRoamingLive = action.value) }
// Remote control & data sources // Remote control & data sources
is SettingsAction.UpdateRC -> settingsRepo.updateRCSettings(action.settings) is SettingsAction.UpdateRC -> settingsRepo.updateRCSettings(action.settings)
is SettingsAction.UpdateRadioControl -> settingsRepo.updateRadioControlSettings(action.settings) is SettingsAction.UpdateRadioControl -> settingsRepo.updateRadioControlSettings(action.settings)