mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
fix(wavelog,radar): sat name LoTW normalization, full grid lines, RX freq restored (4.5.2 overwrite 4)
This commit is contained in:
1 parent
ab1a0c0891
commit
0bcf73e1f9
3 files changed
+103
-35
No files matched your search
@@ -106,6 +106,19 @@ object WaveLogApi {
|
||||
WavelogResult.Success("")
|
||||
}
|
||||
|
||||
/** LoTW 认可的卫星名: 取括号前主名 + 大写(ISS 特判) */
|
||||
fun normalizeSatName(raw: String): String {
|
||||
val main = raw.substringBefore('(').trim()
|
||||
.ifBlank { raw.trim() }
|
||||
.uppercase(Locale.ENGLISH)
|
||||
// ISS 特判: 主名是 ISS/ZARYA/ARISS 变体 → ISS(LoTW 认可名)
|
||||
return when {
|
||||
main == "ZARYA" || main.startsWith("ISS") -> "ISS"
|
||||
main == "ARISS" -> "ISS"
|
||||
else -> main
|
||||
}
|
||||
}
|
||||
|
||||
/** 创建 QSO: 优先 v2, 404 降级 v1(ADIF) */
|
||||
suspend fun postQso(
|
||||
url: String,
|
||||
@@ -117,6 +130,8 @@ object WaveLogApi {
|
||||
val base = normalizeUrl(url)
|
||||
if (base.isBlank()) return@withContext WavelogResult.Failure("服务器地址为空")
|
||||
|
||||
val satName = normalizeSatName(qso.satName)
|
||||
|
||||
// v2: POST /index.php/api/v2/qso(JSON 字段)
|
||||
val v2Body = JSONObject().apply {
|
||||
put("station_profile_id", stationProfileId.toIntOrNull() ?: 0)
|
||||
@@ -128,7 +143,7 @@ object WaveLogApi {
|
||||
put("freq", qso.freqTxHz)
|
||||
put("freq_rx", qso.freqRxHz)
|
||||
put("gridsquare", gridsquare)
|
||||
put("sat_name", qso.satName)
|
||||
put("sat_name", satName)
|
||||
}
|
||||
val (code, resp) = httpRequest("$base/index.php/api/v2/qso", "POST", apiKey, v2Body.toString())
|
||||
if (code in 200..299) return@withContext WavelogResult.Success("已上传 (v2)")
|
||||
@@ -139,7 +154,7 @@ object WaveLogApi {
|
||||
put("key", apiKey)
|
||||
put("station_profile_id", stationProfileId)
|
||||
put("type", "adif")
|
||||
put("string", toAdif(qso, gridsquare))
|
||||
put("string", toAdif(qso, gridsquare, satName))
|
||||
}
|
||||
val (code1, resp1) = httpRequest("$base/index.php/api/qso", "POST", apiKey, v1Body.toString())
|
||||
if (code1 in 200..299) return@withContext WavelogResult.Success("已上传 (v1)")
|
||||
@@ -151,8 +166,8 @@ object WaveLogApi {
|
||||
WavelogResult.Failure("上传失败: v2 HTTP $code, v1 HTTP $code1 — ${shortError(resp1.ifBlank { resp1b })}")
|
||||
}
|
||||
|
||||
/** v1 ADIF 字符串(频率 MHz, 长度=UTF-8 字节数) */
|
||||
private fun toAdif(qso: WavelogQso, gridsquare: String): String {
|
||||
/** v1 ADIF 字符串(频率 MHz, 长度=UTF-8 字节数, sat_name 已规范化) */
|
||||
private fun toAdif(qso: WavelogQso, gridsquare: String, satName: String): String {
|
||||
fun field(name: String, value: String): String {
|
||||
val bytes = value.toByteArray(Charsets.UTF_8).size
|
||||
return "<$name:$bytes>$value"
|
||||
@@ -168,8 +183,8 @@ object WaveLogApi {
|
||||
append(field("qso_date", utcDateCompact(qso.timeUtcMs)))
|
||||
append(field("time_on", utcTimeCompact(qso.timeUtcMs)))
|
||||
if (gridsquare.isNotBlank()) append(field("gridsquare", gridsquare.take(4)))
|
||||
if (qso.satName.isNotBlank()) {
|
||||
append(field("sat_name", qso.satName))
|
||||
if (satName.isNotBlank()) {
|
||||
append(field("sat_name", satName))
|
||||
append(field("prop_mode", "SAT"))
|
||||
}
|
||||
append("<eor>")
|
||||
|
||||
@@ -253,15 +253,19 @@ private fun ExpandedLogInput(
|
||||
showToast(savedMsg)
|
||||
}
|
||||
|
||||
// 频率显示与转发器栏完全一致(不再另算): TX = 当前调谐(txBaseFrequencyHz), 线性附加频段范围
|
||||
// 频率显示与转发器栏完全一致: TX 行 + RX 行(线性附加频段范围)
|
||||
val txForDisplay = txBaseFrequencyHz ?: (radio.uplinkLow ?: radio.downlinkLow ?: 0L)
|
||||
val rxForDisplay = radio.downlinkLow ?: radio.uplinkLow ?: 0L
|
||||
val upLow = radio.uplinkLow
|
||||
val upHigh = radio.uplinkHigh
|
||||
val downLow = radio.downlinkLow
|
||||
val downHigh = radio.downlinkHigh
|
||||
val isLinearRange = upLow != null && upHigh != null && upLow != upHigh
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
// TX 行
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "TX ",
|
||||
text = "TX: ",
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@@ -279,6 +283,27 @@ private fun ExpandedLogInput(
|
||||
)
|
||||
}
|
||||
}
|
||||
// RX 行(与转发器栏 RX 一致)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "RX: ",
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = "${formatFrequency(rxForDisplay)} MHz",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
if (downLow != null && downHigh != null && downLow != downHigh) {
|
||||
Text(
|
||||
text = " (${formatFrequency(downLow)} – ${formatFrequency(downHigh)})",
|
||||
fontSize = 11.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = callsign,
|
||||
onValueChange = { callsign = it.take(12) },
|
||||
|
||||
+54
-26
@@ -2,8 +2,7 @@
|
||||
* WavelogLogScreen.kt — 「日志」页面(更多菜单, 4.5.2 修复)。
|
||||
*
|
||||
* 表格形式展示本地存储的日志: 时间 | 频率 | 卫星 | 呼号 | 已上传(✓)。
|
||||
* 数据来自 WavelogQueue(与雷达页 Log 标签共用)。空态提示; 表格行
|
||||
* 复用左滑删除(同 LogTab 交互)。
|
||||
* 完整格子线(表头横线 + 行横线 + 列竖线 + 外边框); 行复用左滑删除。
|
||||
*/
|
||||
package com.rtbishop.look4sat.feature.radar
|
||||
|
||||
@@ -13,21 +12,20 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -35,6 +33,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.rtbishop.look4sat.core.domain.wavelog.WavelogQueue
|
||||
@@ -44,6 +43,7 @@ import java.util.Calendar
|
||||
import java.util.TimeZone
|
||||
|
||||
private val CheckGreen = Color(0xFF4CAF50)
|
||||
private val GridLineColor = Color(0xFF3A3A3A)
|
||||
|
||||
@Composable
|
||||
fun WavelogLogScreen(
|
||||
@@ -57,29 +57,42 @@ fun WavelogLogScreen(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
// 表格外边框(左右 + 上下由表头/行底线构成)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
// 表头
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 10.dp, vertical = 6.dp)
|
||||
.height(38.dp)
|
||||
) {
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_time), 52.dp)
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_freq), 70.dp)
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_time), 72.dp)
|
||||
GridVLine()
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_freq), 64.dp)
|
||||
GridVLine()
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_sat), 0.dp, weight = 1.2f)
|
||||
GridVLine()
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_call), 0.dp, weight = 1f)
|
||||
GridVLine()
|
||||
HeaderCell(stringResource(id = R.string.wavelog_col_uploaded), 44.dp)
|
||||
}
|
||||
// 表头横线
|
||||
HorizontalDivider(thickness = 1.5.dp, color = GridLineColor)
|
||||
// 表头与数据区留白
|
||||
Box(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wavelog_empty),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 12.dp)
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 12.dp)
|
||||
)
|
||||
} else {
|
||||
entries.forEachIndexed { index, entry ->
|
||||
@@ -94,13 +107,16 @@ fun WavelogLogScreen(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp)
|
||||
.height(36.dp)
|
||||
) {
|
||||
Cell(formatLocalTime(entry.timeUtcMs), 74.dp)
|
||||
Cell(formatFrequency(entry.freqTxHz), 62.dp)
|
||||
Cell(formatLocalTime(entry.timeUtcMs), 72.dp)
|
||||
GridVLine()
|
||||
Cell(formatFrequency(entry.freqTxHz), 64.dp)
|
||||
GridVLine()
|
||||
Cell(entry.satName, 0.dp, weight = 1.2f)
|
||||
GridVLine()
|
||||
Cell(entry.call, 0.dp, weight = 1f)
|
||||
GridVLine()
|
||||
Box(modifier = Modifier.width(44.dp)) {
|
||||
if (entry.uploaded) {
|
||||
Text(
|
||||
@@ -112,11 +128,9 @@ fun WavelogLogScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
// 表格分隔线
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f)
|
||||
)
|
||||
// 行分隔线
|
||||
HorizontalDivider(thickness = 1.dp, color = GridLineColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,32 +138,46 @@ fun WavelogLogScreen(
|
||||
}
|
||||
}
|
||||
|
||||
/** 列竖线 */
|
||||
@Composable
|
||||
private fun RowScope.HeaderCell(text: String, width: androidx.compose.ui.unit.Dp, weight: Float = 0f) {
|
||||
private fun GridVLine() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.fillMaxHeight()
|
||||
.background(GridLineColor)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.HeaderCell(text: String, width: Dp, weight: Float = 0f) {
|
||||
val mod = if (weight > 0f) Modifier.weight(weight) else Modifier.width(width)
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 11.sp,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
maxLines = 1,
|
||||
modifier = mod
|
||||
modifier = mod.padding(horizontal = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Cell(text: String, width: androidx.compose.ui.unit.Dp, weight: Float = 0f) {
|
||||
private fun RowScope.Cell(text: String, width: Dp, weight: Float = 0f) {
|
||||
val mod = if (weight > 0f) Modifier.weight(weight) else Modifier.width(width)
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 12.sp,
|
||||
maxLines = 1,
|
||||
modifier = mod
|
||||
modifier = mod.padding(horizontal = 6.dp)
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatLocalTime(utcMs: Long): String {
|
||||
val cal = Calendar.getInstance(TimeZone.getDefault())
|
||||
cal.timeInMillis = utcMs
|
||||
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))
|
||||
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)
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user