diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/wavelog/WavelogQueue.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/wavelog/WavelogQueue.kt
index 0fd829bf..3e728c21 100644
--- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/wavelog/WavelogQueue.kt
+++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/wavelog/WavelogQueue.kt
@@ -25,6 +25,7 @@ data class WavelogQso(
val freqTxHz: Long, // 上行(回车那一秒多普勒修正)
val freqRxHz: Long, // 下行
val satName: String,
+ val sessionId: String = "", // 场次 ID: 卫星名-AOS 时间戳(过境仰角 0 秒), 空=未分组(旧数据)
val uploaded: Boolean = false // 是否已成功上传(4.5.2 修复: 成功后保留标记, 表格打勾)
)
@@ -46,6 +47,7 @@ class WavelogQueue(private val store: IWavelogQueueStore) {
freqTxHz = o.optLong("freqTxHz"),
freqRxHz = o.optLong("freqRxHz"),
satName = o.optString("satName"),
+ sessionId = o.optString("sessionId"),
uploaded = o.optBoolean("uploaded", false)
)
}
@@ -89,6 +91,7 @@ class WavelogQueue(private val store: IWavelogQueueStore) {
put("id", q.id); put("timeUtcMs", q.timeUtcMs); put("call", q.call)
put("mode", q.mode); put("freqTxHz", q.freqTxHz)
put("freqRxHz", q.freqRxHz); put("satName", q.satName)
+ put("sessionId", q.sessionId)
put("uploaded", q.uploaded)
})
}
diff --git a/core/presentation/src/main/res/values-in/strings.xml b/core/presentation/src/main/res/values-in/strings.xml
index 42aebd52..a055c8b3 100644
--- a/core/presentation/src/main/res/values-in/strings.xml
+++ b/core/presentation/src/main/res/values-in/strings.xml
@@ -203,6 +203,7 @@
Satelit
Panggilan
Terkirim
+ Tidak dikelompokkan
Pengaturan lainnya
Pengaturan UI
Urutan halaman
diff --git a/core/presentation/src/main/res/values-tr/strings.xml b/core/presentation/src/main/res/values-tr/strings.xml
index 81d1360c..df14a0de 100644
--- a/core/presentation/src/main/res/values-tr/strings.xml
+++ b/core/presentation/src/main/res/values-tr/strings.xml
@@ -213,6 +213,7 @@
Uydu
Çağrı
Gönderildi
+ Gruplanmamış
Diğer ayarlar
UI Ayarları
Sayfa sırası
diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml
index 2c789b08..97ae0f51 100644
--- a/core/presentation/src/main/res/values-zh/strings.xml
+++ b/core/presentation/src/main/res/values-zh/strings.xml
@@ -196,6 +196,7 @@
卫星
呼号
已传
+ 未分组
其他设置
UI设置
页面顺序
diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml
index 14b19369..1324d113 100644
--- a/core/presentation/src/main/res/values/strings.xml
+++ b/core/presentation/src/main/res/values/strings.xml
@@ -227,6 +227,7 @@
Satellite
Call
Sent
+ Ungrouped
Other settings
UI Settings
Page order
diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/LogTab.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/LogTab.kt
index e892617c..61cd4209 100644
--- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/LogTab.kt
+++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/LogTab.kt
@@ -88,6 +88,7 @@ fun LogTab(
wavelogConfigured: Boolean,
showToast: (String) -> Unit,
txBaseFrequencyHz: Long? = null,
+ aosTimeMs: Long = 0L,
modifier: Modifier = Modifier
) {
var selectedUuid by remember { mutableStateOf(null) }
@@ -159,6 +160,7 @@ fun LogTab(
queue = queue,
showToast = showToast,
txBaseFrequencyHz = txBaseFrequencyHz,
+ aosTimeMs = aosTimeMs,
onSaved = { refreshTick++ }
)
}
@@ -225,6 +227,7 @@ private fun ExpandedLogInput(
queue: WavelogQueue,
showToast: (String) -> Unit,
txBaseFrequencyHz: Long? = null,
+ aosTimeMs: Long = 0L,
onSaved: () -> Unit
) {
var callsign by remember { mutableStateOf("") }
@@ -246,7 +249,8 @@ private fun ExpandedLogInput(
mode = mode.trim().ifBlank { "FM" }.uppercase(),
freqTxHz = tx,
freqRxHz = rx,
- satName = satelliteName
+ satName = satelliteName,
+ sessionId = buildSessionId(satelliteName, aosTimeMs)
)
)
callsign = ""
@@ -448,3 +452,14 @@ private fun formatLocalTime(utcMs: Long): String {
cal.timeInMillis = utcMs
return "%02d:%02d".format(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE))
}
+
+/** 场次 ID: 卫星名-AOS 时间戳(UTC yyyyMMdd-HHmm); 无 AOS 时用回车时刻 */
+private fun buildSessionId(satName: String, aosTimeMs: Long): String {
+ val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ cal.timeInMillis = if (aosTimeMs > 0) aosTimeMs else System.currentTimeMillis()
+ val stamp = "%04d%02d%02d-%02d%02d".format(
+ cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH),
+ cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)
+ )
+ return "$satName-$stamp"
+}
diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt
index e3febb88..ae30c2c9 100644
--- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt
+++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/RadarScreen.kt
@@ -299,7 +299,8 @@ private fun PagerCard(
queue = wavelogQueue,
wavelogConfigured = wavelogConfigured,
showToast = showToast,
- txBaseFrequencyHz = uiState.radioControl.txBaseFrequencyHz
+ txBaseFrequencyHz = uiState.radioControl.txBaseFrequencyHz,
+ aosTimeMs = uiState.currentPass?.aosTime ?: 0L
)
}
}
diff --git a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/WavelogLogScreen.kt b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/WavelogLogScreen.kt
index 44b0fef5..4e3c001c 100644
--- a/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/WavelogLogScreen.kt
+++ b/feature/radar/src/main/java/com/rtbishop/look4sat/feature/radar/WavelogLogScreen.kt
@@ -95,7 +95,27 @@ fun WavelogLogScreen(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 12.dp)
)
} else {
- entries.forEachIndexed { index, entry ->
+ // 按场次分组(sessionId = 卫星名-AOS 时间戳); 空 sessionId 归"未分组"放最后
+ val groups = entries.groupBy { it.sessionId.ifBlank { UNGROUPED } }
+ .toSortedMap(compareByDescending { id ->
+ entries.filter { it.sessionId.ifBlank { UNGROUPED } == id }.minOfOrNull { it.timeUtcMs } ?: 0L
+ }.thenByDescending { it })
+ groups.forEach { (sessionId, groupEntries) ->
+ // 组标题(场次名 + 本地时间)
+ val (satPart, timePart) = parseSession(sessionId)
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = if (sessionId == UNGROUPED) stringResource(id = R.string.wavelog_ungrouped)
+ else "$satPart - ${formatSessionTime(timePart)}",
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)
+ )
+ }
+ // 组间横线(md --- 效果)
+ HorizontalDivider(thickness = 1.5.dp, color = GridLineColor)
+ groupEntries.forEach { entry ->
SwipeDeleteRow(
onDelete = {
queue.remove(entry.id)
@@ -133,6 +153,7 @@ fun WavelogLogScreen(
}
}
}
+ }
}
}
}
@@ -173,6 +194,38 @@ private fun RowScope.Cell(text: String, width: Dp, weight: Float = 0f) {
)
}
+private const val UNGROUPED = "__ungrouped__"
+
+/** 解析场次 ID: "ASRTU-1-20260804-2014" → (卫星名, "20260804-2014") */
+private fun parseSession(sessionId: String): Pair {
+ val parts = sessionId.split('-')
+ if (parts.size < 3) return sessionId to ""
+ val timePart = parts.takeLast(2).joinToString("-")
+ val satPart = parts.dropLast(2).joinToString("-")
+ return satPart to timePart
+}
+
+/** 场次时间(UTC "20260804-2014") → 本地 "MM-dd HH:mm" */
+private fun formatSessionTime(timePart: String): String {
+ if (timePart.length != 13 || timePart[8] != '-') return timePart
+ return try {
+ val year = timePart.substring(0, 4).toInt()
+ val month = timePart.substring(4, 6).toInt()
+ val day = timePart.substring(6, 8).toInt()
+ val hour = timePart.substring(9, 11).toInt()
+ val minute = timePart.substring(11, 13).toInt()
+ val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ cal.set(year, month - 1, day, hour, minute, 0)
+ cal.timeInMillis = cal.timeInMillis + TimeZone.getDefault().getOffset(cal.timeInMillis)
+ "%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)
+ )
+ } catch (_: Exception) {
+ timePart
+ }
+}
+
private fun formatLocalTime(utcMs: Long): String {
val cal = Calendar.getInstance(TimeZone.getDefault())
cal.timeInMillis = utcMs