feat(mutual): mutual page times follow the UTC setting

The pass card time range and the elevation-curve X-axis labels were hardcoded
to the device timezone. MutualUiState now carries isUtc, initialized and kept
in sync with OtherSettings.stateOfUtc (same pattern as PassesViewModel), and
both the card timeFormat and the chart axis use UTC when the setting is on.

Also fixes pre-existing test-fake compile breakage: FakeSettingsRepo built
OtherSettings with positional args missing shouldSeeWhatsNew, and never
implemented the wavelogSettings/lotwSettings/worked-grids members added by
later interface extensions.
This commit is contained in:
atsunatsu committed 2026-09-16 11:57:03 +08:00
1 parent 06f00e1601
commit 1bed7849a3
4 files changed
+50 -4

No files matched your search

@@ -45,6 +45,7 @@ import com.rtbishop.look4sat.core.presentation.R
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.TimeZone
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
@@ -58,6 +59,7 @@ fun ElevationCurveChart(
startTime: Long, startTime: Long,
endTime: Long, endTime: Long,
maxElev: Double, maxElev: Double,
isUtc: Boolean = false,
progress: Float = 0.5f, progress: Float = 0.5f,
onProgressChange: (Float) -> Unit = {}, onProgressChange: (Float) -> Unit = {},
modifier: Modifier = Modifier modifier: Modifier = Modifier
@@ -74,7 +76,11 @@ fun ElevationCurveChart(
val textColorArgb = textColor.toArgb() val textColorArgb = textColor.toArgb()
val onSurfaceArgb = onSurfaceColor.toArgb() val onSurfaceArgb = onSurfaceColor.toArgb()
val gridColorArgb = gridColor.toArgb() val gridColorArgb = gridColor.toArgb()
val timeFormat = remember { SimpleDateFormat("HH:mm", Locale.getDefault()) } val timeFormat = remember(isUtc) {
SimpleDateFormat("HH:mm", Locale.getDefault()).apply {
if (isUtc) timeZone = TimeZone.getTimeZone("UTC")
}
}
Column(modifier = modifier.fillMaxWidth()) { Column(modifier = modifier.fillMaxWidth()) {
Canvas( Canvas(
@@ -71,6 +71,7 @@ import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.TimeZone
@Composable @Composable
fun MutualScreen( fun MutualScreen(
@@ -186,7 +187,11 @@ private fun MutualContent(
onHoursAhead: (Int) -> Unit, onHoursAhead: (Int) -> Unit,
onClearError: () -> Unit onClearError: () -> Unit
) { ) {
val timeFormat = remember { SimpleDateFormat("MM/dd HH:mm", Locale.getDefault()) } val timeFormat = remember(state.isUtc) {
SimpleDateFormat("MM/dd HH:mm", Locale.getDefault()).apply {
if (state.isUtc) timeZone = TimeZone.getTimeZone("UTC")
}
}
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@@ -323,6 +328,7 @@ private fun MutualContent(
pass = pass, pass = pass,
isExpanded = state.selectedPassIndex == index, isExpanded = state.selectedPassIndex == index,
timeFormat = timeFormat, timeFormat = timeFormat,
isUtc = state.isUtc,
minElevA = state.stationAMinElev, minElevA = state.stationAMinElev,
minElevB = state.stationBMinElev, minElevB = state.stationBMinElev,
onClick = { onSelectPass(if (state.selectedPassIndex == index) -1 else index) }, onClick = { onSelectPass(if (state.selectedPassIndex == index) -1 else index) },
@@ -527,6 +533,7 @@ private fun MutualPassCard(
pass: MutualPass, pass: MutualPass,
isExpanded: Boolean, isExpanded: Boolean,
timeFormat: SimpleDateFormat, timeFormat: SimpleDateFormat,
isUtc: Boolean,
minElevA: Double, minElevA: Double,
minElevB: Double, minElevB: Double,
onClick: () -> Unit, onClick: () -> Unit,
@@ -622,6 +629,7 @@ private fun MutualPassCard(
startTime = visibleStart, startTime = visibleStart,
endTime = visibleEnd, endTime = visibleEnd,
maxElev = adjustedMaxElev, maxElev = adjustedMaxElev,
isUtc = isUtc,
progress = dragProgress, progress = dragProgress,
onProgressChange = { dragProgress = it } onProgressChange = { dragProgress = it }
) )
@@ -33,6 +33,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -53,6 +54,7 @@ data class MutualUiState(
val isCalculating: Boolean = false, val isCalculating: Boolean = false,
val hasSearched: Boolean = false, val hasSearched: Boolean = false,
val selectedPassIndex: Int = -1, val selectedPassIndex: Int = -1,
val isUtc: Boolean = false,
val errorMessage: String? = null val errorMessage: String? = null
) )
@@ -77,8 +79,15 @@ class MutualViewModel(
stationALat = "%.4f".format(pos.latitude), stationALat = "%.4f".format(pos.latitude),
stationALon = "%.4f".format(pos.longitude), stationALon = "%.4f".format(pos.longitude),
stationAMinElev = 0.0, stationAMinElev = 0.0,
stationBMinElev = 0.0 stationBMinElev = 0.0,
isUtc = settingsRepo.otherSettings.value.stateOfUtc
) } ) }
// Keep the time display in sync with the UTC toggle in Settings
viewModelScope.launch {
settingsRepo.otherSettings.collectLatest { settings ->
_uiState.update { it.copy(isUtc = settings.stateOfUtc) }
}
}
} }
fun onStationALat(value: String) { fun onStationALat(value: String) {
@@ -2,11 +2,14 @@ package com.rtbishop.look4sat.feature.mutual
import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings
import com.rtbishop.look4sat.core.domain.model.DatabaseState import com.rtbishop.look4sat.core.domain.model.DatabaseState
import com.rtbishop.look4sat.core.domain.model.GridQso
import com.rtbishop.look4sat.core.domain.model.LoTWSettings
import com.rtbishop.look4sat.core.domain.model.OtherSettings import com.rtbishop.look4sat.core.domain.model.OtherSettings
import com.rtbishop.look4sat.core.domain.model.PassesSettings import com.rtbishop.look4sat.core.domain.model.PassesSettings
import com.rtbishop.look4sat.core.domain.model.RCSettings import com.rtbishop.look4sat.core.domain.model.RCSettings
import com.rtbishop.look4sat.core.domain.model.RadioControlSettings import com.rtbishop.look4sat.core.domain.model.RadioControlSettings
import com.rtbishop.look4sat.core.domain.model.SatRadio import com.rtbishop.look4sat.core.domain.model.SatRadio
import com.rtbishop.look4sat.core.domain.model.WavelogSettings
import com.rtbishop.look4sat.core.domain.predict.GeoPos import com.rtbishop.look4sat.core.domain.predict.GeoPos
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
@@ -62,7 +65,17 @@ class FakeSettingsRepo(initialPosition: GeoPos = GeoPos(23.13, 113.26)) : ISetti
RCSettings(false, "", "", "", false, "", "", "", 0L, false, "", "", "", false, "", "") RCSettings(false, "", "", "", false, "", "", "", 0L, false, "", "", "", false, "", "")
) )
override val otherSettings: StateFlow<OtherSettings> = MutableStateFlow( override val otherSettings: StateFlow<OtherSettings> = MutableStateFlow(
OtherSettings(false, false, false, false, false, false, false, false) OtherSettings(
stateOfAutoUpdate = false,
stateOfSensors = false,
stateOfSweep = false,
stateOfUtc = false,
stateOfLightTheme = false,
stateOfNightMode = false,
stateOfMapGrid = false,
shouldSeeWarning = false,
shouldSeeWhatsNew = false
)
) )
override val dataSourcesSettings: StateFlow<DataSourcesSettings> = MutableStateFlow( override val dataSourcesSettings: StateFlow<DataSourcesSettings> = MutableStateFlow(
DataSourcesSettings(satelliteUrls = emptyList(), transceiversUrls = emptyList()) DataSourcesSettings(satelliteUrls = emptyList(), transceiversUrls = emptyList())
@@ -70,6 +83,8 @@ class FakeSettingsRepo(initialPosition: GeoPos = GeoPos(23.13, 113.26)) : ISetti
override val radioControlSettings: StateFlow<RadioControlSettings> = MutableStateFlow( override val radioControlSettings: StateFlow<RadioControlSettings> = MutableStateFlow(
RadioControlSettings(false, RadioControlSettings.MODEL_YAESU_FT817, "", "", "", "", 9600) RadioControlSettings(false, RadioControlSettings.MODEL_YAESU_FT817, "", "", "", "", 9600)
) )
override val wavelogSettings: StateFlow<WavelogSettings> = MutableStateFlow(WavelogSettings())
override val lotwSettings: StateFlow<LoTWSettings> = MutableStateFlow(LoTWSettings())
override fun setSelectedIds(ids: List<Int>) = TODO() override fun setSelectedIds(ids: List<Int>) = TODO()
override fun setSelectedTypes(types: List<String>) = TODO() override fun setSelectedTypes(types: List<String>) = TODO()
@@ -93,4 +108,12 @@ class FakeSettingsRepo(initialPosition: GeoPos = GeoPos(23.13, 113.26)) : ISetti
override fun setSatelliteOffset(catnum: Int, offset: String) = TODO() override fun setSatelliteOffset(catnum: Int, offset: String) = TODO()
override fun getAmSatCallsign(): String = "" override fun getAmSatCallsign(): String = ""
override fun setAmSatCallsign(callsign: String) = TODO() override fun setAmSatCallsign(callsign: String) = TODO()
override fun updateWavelogSettings(settings: WavelogSettings) = TODO()
override fun getWorkedGrids(): Set<String> = TODO()
override fun setWorkedGrids(grids: Set<String>) = TODO()
override fun getWorkedGridQsos(): Map<String, List<GridQso>> = TODO()
override fun setWorkedGridQsos(qsos: Map<String, List<GridQso>>) = TODO()
override fun getRoamedGrids(): Set<String> = TODO()
override fun setRoamedGrids(grids: Set<String>) = TODO()
override fun updateLoTWSettings(settings: LoTWSettings) = TODO()
} }