fix(nav,radar,wavelog): 4.5.2 third overwrite - log page entry visible, freq format 3 decimals, direct freq extraction

User-test fixes (3rd overwrite, version stays 4.5.2/452):

1. Log page was invisible everywhere outside the radar tab:
   - allNavItems in MainScreen.kt was missing Screen.WavelogLog ->
     not in bottom nav, not in More menu
   - SettingsScreen UI-order lists were missing the WavelogLog row ->
     not in page order settings either
   Now the Log page appears in the More menu (default sub menu tail,
   migration appends it) AND in UI settings page-order lists.
2. Frequency display: formatFrequency was MHz.kHz.Hz (145.900.000);
   now MHz.kHz (145.900) per request. Applies to transceiver panel
   and Log page alike (shared formatter).
3. Log tab frequency: now extracts the exact numbers shown in the
   transceiver panel (txBaseFrequencyHz for TX, uplinkLow/uplinkHigh
   range for linear) - no re-computation, no extra decimals. Upload
   uses the same tuned frequency (Hz precision kept internally).
4. Log page (More menu) table: date+time column (MM-dd HH:mm),
   row separators (table lines), uploaded checkmark kept.

Verified: check_strings OK (8 files); :app:compileDebugKotlin
BUILD SUCCESSFUL (includes upstream merge 1a552417).
This commit is contained in:
mckero committed 2026-08-04 12:07:45 +00:00
1 parent 1a55241791
commit ab1a0c0891
5 files changed
+53 -61

No files matched your search

@@ -148,7 +148,7 @@ fun MainScreen(navigateToRadar: () -> Unit = {}) {
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
// UI 设置: 按 screenOrder 排序(空 = 默认顺序), 再按 hiddenScreens 过滤(设置页固定保留)
val allNavItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Mutual, Screen.Roaming, Screen.CwDecode, Screen.Map, Screen.Settings)
val allNavItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Mutual, Screen.Roaming, Screen.CwDecode, Screen.WavelogLog, Screen.Map, Screen.Settings)
.sortedBy { screen ->
// 未知(新页面如 CwDecode 不在旧持久化顺序里): 用默认顺序位置(漫游↔地图), 再兜底最后
val idx = otherSettings.screenOrder.indexOf(screen.screenId)
@@ -518,6 +518,5 @@ fun formatFrequency(frequencyHz: Long): String {
if (frequencyHz <= 0) return "---"
val mhz = frequencyHz / 1_000_000
val khz = (frequencyHz % 1_000_000) / 1_000
val hz = frequencyHz % 1_000
return String.format(Locale.ENGLISH, "%d.%03d.%03d", mhz, khz, hz)
return String.format(Locale.ENGLISH, "%d.%03d", mhz, khz)
}
@@ -229,26 +229,14 @@ private fun ExpandedLogInput(
var callsign by remember { mutableStateOf("") }
var mode by remember { mutableStateOf(radio.uplinkMode ?: "FM") }
// 回车那一刻的频率(与雷达页 Doppler 面板一致): 优先用当前调谐 TX 频率
fun currentFreqs(): Pair<Long, Long> {
val uplinkBase = radio.uplinkLow ?: radio.downlinkLow ?: 0L
val downlinkBase = radio.downlinkLow ?: radio.uplinkLow ?: 0L
// 调谐基准(用户在当前转发器上调的)优先; 否则转发器标称下限
val tx = txBaseFrequencyHz ?: (if (orbitalPos != null) orbitalPos.getUplinkFreq(uplinkBase) else uplinkBase)
val rx = if (orbitalPos != null) {
// 线性转发器: 按映射算下行; FM: 标称下行加多普勒
DopplerFrequencyCalculator.computeDownlinkFromUplink(tx, radio, orbitalPos)
?: orbitalPos.getDownlinkFreq(downlinkBase)
} else downlinkBase
return tx to rx
}
val savedMsg = stringResource(id = R.string.wavelog_saved)
fun submit() {
val call = callsign.trim().uppercase()
if (call.length < 3) return
val (tx, rx) = currentFreqs()
// 频率直接取转发器栏的数字(不再多普勒再计算): TX = 当前调谐, RX = 下行基准
val tx = txBaseFrequencyHz ?: (radio.uplinkLow ?: radio.downlinkLow ?: 0L)
val rx = radio.downlinkLow ?: radio.uplinkLow ?: 0L
queue.add(
WavelogQso(
id = UUID.randomUUID().toString(),
@@ -265,35 +253,31 @@ private fun ExpandedLogInput(
showToast(savedMsg)
}
val (txHz, rxHz) = currentFreqs()
// 线性转发器: 显示频段区间(多普勒修正); FM: 单频
val isLinear = DopplerFrequencyCalculator.isLinearTransponder(radio)
val uplinkLow = radio.uplinkLow
val uplinkHigh = radio.uplinkHigh
val downlinkLow = radio.downlinkLow
val downlinkHigh = radio.downlinkHigh
val txRange = if (isLinear && uplinkLow != null && uplinkHigh != null && orbitalPos != null) {
val lo = orbitalPos.getUplinkFreq(uplinkLow)
val hi = orbitalPos.getUplinkFreq(uplinkHigh)
"${formatFrequency(minOf(lo, hi))}–${formatFrequency(maxOf(lo, hi))}"
} else null
val rxRange = if (isLinear && downlinkLow != null && downlinkHigh != null && orbitalPos != null) {
val lo = orbitalPos.getDownlinkFreq(downlinkLow)
val hi = orbitalPos.getDownlinkFreq(downlinkHigh)
"${formatFrequency(minOf(lo, hi))}–${formatFrequency(maxOf(lo, hi))}"
} else null
// 频率显示与转发器栏完全一致(不再另算): TX = 当前调谐(txBaseFrequencyHz), 线性附加频段范围
val txForDisplay = txBaseFrequencyHz ?: (radio.uplinkLow ?: radio.downlinkLow ?: 0L)
val upLow = radio.uplinkLow
val upHigh = radio.uplinkHigh
val isLinearRange = upLow != null && upHigh != null && upLow != upHigh
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "TX ${txRange ?: formatFrequency(txHz)}",
text = "TX ",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = "RX ${rxRange ?: formatFrequency(rxHz)}",
fontSize = 12.sp,
text = "${formatFrequency(txForDisplay)} MHz",
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary
)
if (isLinearRange) {
Text(
text = " (${formatFrequency(upLow)} – ${formatFrequency(upHigh)})",
fontSize = 11.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
OutlinedTextField(
value = callsign,
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -81,34 +82,41 @@ fun WavelogLogScreen(
modifier = Modifier.padding(vertical = 12.dp)
)
} else {
entries.forEach { entry ->
entries.forEachIndexed { index, entry ->
SwipeDeleteRow(
onDelete = {
queue.remove(entry.id)
refreshTick++
}
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceContainer)
.padding(horizontal = 10.dp, vertical = 8.dp)
) {
Cell(formatLocalTime(entry.timeUtcMs), 52.dp)
Cell(formatFrequency(entry.freqTxHz), 70.dp)
Cell(entry.satName, 0.dp, weight = 1.2f)
Cell(entry.call, 0.dp, weight = 1f)
Box(modifier = Modifier.width(44.dp)) {
if (entry.uploaded) {
Text(
text = "✓",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = CheckGreen
)
Column(modifier = Modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceContainer)
.padding(horizontal = 10.dp, vertical = 8.dp)
) {
Cell(formatLocalTime(entry.timeUtcMs), 74.dp)
Cell(formatFrequency(entry.freqTxHz), 62.dp)
Cell(entry.satName, 0.dp, weight = 1.2f)
Cell(entry.call, 0.dp, weight = 1f)
Box(modifier = Modifier.width(44.dp)) {
if (entry.uploaded) {
Text(
text = "✓",
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = CheckGreen
)
}
}
}
// 表格分隔线
HorizontalDivider(
thickness = 1.dp,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f)
)
}
}
}
@@ -143,5 +151,5 @@ private fun RowScope.Cell(text: String, width: androidx.compose.ui.unit.Dp, weig
private fun formatLocalTime(utcMs: Long): String {
val cal = Calendar.getInstance(TimeZone.getDefault())
cal.timeInMillis = utcMs
return "%02d:%02d".format(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE))
return "%02d-%02d %02d:%02d".format(cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE))
}
@@ -704,6 +704,7 @@ private fun UiSettingsCard(
R.string.nav_mutual to "Mutual",
R.string.nav_roaming to "Roaming",
R.string.nav_cw to "CwDecode",
R.string.nav_log to "WavelogLog",
R.string.nav_map to "Map",
R.string.nav_prefs to "Settings"
) // name 必须与 Screen.screenId 一致 (R8 安全)