mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-07 21:17:23 +00:00
feat(wavelog): 二选一互斥模式 + p12 空密码导入修复(4.4.8-ba7opf.9.4, 569)
- LoTW/Wavelog 互斥:isReady 即 Wavelog 模式,LoTW 卡置灰+说明,上传按模式分流(雷达页/日志本),自动 LoTW 同步门控;配置切换弹警告、可清除配置 - 同步重做:get_contacts_adif 每台址游标,增量/全量双按钮,只增不删,URL 变更增量降级全量;同步刷新日志本+地图 workedGrids/gridQsos/roamedGrids - 日志本台址选择器(全部/各台址)+ Wavelog 上传预览弹窗;Qso 增 wavelogStation,DB v4→v5 - 删旧 WavelogRepository/wavelogSettings 链路,换 WavelogSyncRepository + WavelogSync 纯函数 - p12 修复:导入按钮放开空密码(isEnabled=!busy)+ 密码框留空提示;Pkcs12ReaderTest +4 用例 - 单测六模块全绿;版本 4.4.8-ba7opf.9.4 (569)
This commit is contained in:
1 parent
1fa7dd2d88
commit
09539ff13a
40 files changed
+1715
-470
No files matched your search
@@ -68,6 +68,7 @@ import com.rtbishop.look4sat.core.presentation.sheetDialogContainerColor
|
||||
import com.rtbishop.look4sat.core.presentation.sheetDialogShape
|
||||
import com.rtbishop.look4sat.core.presentation.gridsLabel
|
||||
import com.rtbishop.look4sat.core.presentation.LoTWPositionWarningDialog
|
||||
import com.rtbishop.look4sat.core.presentation.WavelogUploadPreviewDialog
|
||||
|
||||
@Composable
|
||||
fun LogPage(
|
||||
@@ -212,7 +213,7 @@ fun LogPage(
|
||||
onClick = logViewModel::prepareUpload,
|
||||
enabled = !logUiState.busy,
|
||||
modifier = Modifier.weight(1f)
|
||||
) { Text("上传 LoTW", fontSize = 13.sp) }
|
||||
) { Text(if (logUiState.wavelogMode) "上传 Wavelog" else "上传 LoTW", fontSize = 13.sp) }
|
||||
}
|
||||
if (logUiState.busy) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
@@ -260,19 +261,27 @@ fun LogPage(
|
||||
UploadPreviewDialog(
|
||||
preview = preview,
|
||||
busy = logUiState.busy,
|
||||
wavelogCount = logUiState.wavelogPending?.count ?: 0,
|
||||
onConfirm = logViewModel::confirmUpload,
|
||||
onDismiss = logViewModel::dismissPreview
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logUiState.wavelogPreview?.let { preview ->
|
||||
WavelogUploadPreviewDialog(
|
||||
preview = preview,
|
||||
busy = logUiState.busy,
|
||||
onConfirm = logViewModel::confirmWavelogUpload,
|
||||
onDismiss = logViewModel::dismissWavelogPreview
|
||||
)
|
||||
}
|
||||
|
||||
if (logUiState.message.isNotBlank()) {
|
||||
AlertDialog(
|
||||
onDismissRequest = logViewModel::clearMessage,
|
||||
shape = sheetDialogShape(),
|
||||
containerColor = sheetDialogContainerColor(),
|
||||
title = { SheetDialogTitle("LoTW Upload") },
|
||||
title = { SheetDialogTitle(if (logUiState.wavelogMode) "Wavelog Upload" else "LoTW Upload") },
|
||||
text = { Text(logUiState.message) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = logViewModel::clearMessage) { Text("OK") }
|
||||
@@ -407,7 +416,6 @@ private fun PostDialog(
|
||||
private fun UploadPreviewDialog(
|
||||
preview: com.rtbishop.look4sat.core.domain.repository.LoTWUploadPreview,
|
||||
busy: Boolean,
|
||||
wavelogCount: Int = 0,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
@@ -456,13 +464,6 @@ private fun UploadPreviewDialog(
|
||||
}
|
||||
Text(parts.joinToString(" · "), fontSize = 12.sp, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
if (wavelogCount > 0) {
|
||||
Text(
|
||||
"同时同步 Wavelog:$wavelogCount 条",
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -86,9 +86,10 @@ data class LogUiState(
|
||||
val message: String = "",
|
||||
val postText: String? = null,
|
||||
val busy: Boolean = false,
|
||||
/** Wavelog piggyback: batch prepared alongside the LoTW preview (see prepareUpload);
|
||||
* uploaded right after a confirmed LoTW upload. Null when Wavelog is not ready. */
|
||||
val wavelogPending: WavelogUploadPreview? = null,
|
||||
/** True when uploads route through Wavelog (Wavelog configured — LoTW paused). */
|
||||
val wavelogMode: Boolean = false,
|
||||
/** Wavelog mode: the prepared batch awaiting the operator's confirmation. */
|
||||
val wavelogPreview: WavelogUploadPreview? = null,
|
||||
/** Out-of-window contact waiting for the operator's confirmation (null when closed). */
|
||||
val outOfWindowLog: OutOfWindowLog? = null
|
||||
)
|
||||
@@ -124,6 +125,13 @@ class LogViewModel(
|
||||
)
|
||||
}
|
||||
}
|
||||
// Upload routing follows the Wavelog configuration: once it is ready the log
|
||||
// page uploads through Wavelog and skips LoTW entirely (the one-of-two rule).
|
||||
viewModelScope.launch {
|
||||
settingsRepo.wavelogUploadSettings.collect { settings ->
|
||||
_uiState.update { it.copy(wavelogMode = settings.isReady) }
|
||||
}
|
||||
}
|
||||
// The roaming hint tracks the freshest position data: refresh on init and every fix.
|
||||
viewModelScope.launch {
|
||||
settingsRepo.stationPosition.collect { refreshPositionHintNow() }
|
||||
@@ -268,20 +276,16 @@ class LogViewModel(
|
||||
_uiState.update { it.copy(busy = true, message = "") }
|
||||
try {
|
||||
val all = qsoRepository.records.first()
|
||||
val wavelogSettings = settingsRepo.wavelogUploadSettings.value
|
||||
if (wavelogSettings.isReady) {
|
||||
// Wavelog mode: the upload goes to Wavelog only (LoTW stays out).
|
||||
prepareWavelogUpload(all, wavelogSettings)
|
||||
return@launch
|
||||
}
|
||||
// Only local (non-confirmed) records are candidates for upload;
|
||||
// LoTW-imported confirmations are the feedback side.
|
||||
val pending = all.filter { !it.lotwConfirmed && !it.lotwUploaded && it.status == QsoStatus.COMPLETE }
|
||||
val audit = lotwUploadRepository.audit(pending)
|
||||
// Wavelog piggyback (option A): prepare its batch while we are here, so
|
||||
// the LoTW preview can show the count and confirm can post it directly.
|
||||
val wavelogSettings = settingsRepo.wavelogUploadSettings.value
|
||||
val wavelogPreview = if (wavelogSettings.isReady) {
|
||||
runCatching {
|
||||
wavelogUploadRepository.prepare(wavelogUploadCandidates(all), wavelogSettings)
|
||||
}.getOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (audit.pending == 0) {
|
||||
val msg = when {
|
||||
audit.unavailable > 0 -> unavailableUploadSummary(
|
||||
@@ -291,14 +295,7 @@ class LogViewModel(
|
||||
audit.unknown > 0 -> "${audit.unknown} QSO(s) had an unknown upload result — not retried automatically"
|
||||
else -> "No pending QSOs to upload"
|
||||
}
|
||||
// No LoTW work to do — still flush the Wavelog backlog (catch-up): a
|
||||
// freshly configured Wavelog syncs without waiting for new QSOs.
|
||||
val wavelogMsg = wavelogPreview?.let {
|
||||
if (it.count > 0) uploadWavelogNow(it, wavelogSettings) else wavelogIdleSegment(it)
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(busy = false, message = listOfNotNull(msg, wavelogMsg).joinToString("\n"))
|
||||
}
|
||||
_uiState.update { it.copy(busy = false, message = msg) }
|
||||
return@launch
|
||||
}
|
||||
val preview = lotwUploadRepository.prepare(pending, false)
|
||||
@@ -309,7 +306,6 @@ class LogViewModel(
|
||||
it.copy(
|
||||
busy = false,
|
||||
preview = preview,
|
||||
wavelogPending = wavelogPreview,
|
||||
uploadPositionWarning = positionWarning(settingsRepo.getCurrentGrid(), preview.grids)
|
||||
)
|
||||
}
|
||||
@@ -321,6 +317,24 @@ class LogViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/** Wavelog-mode branch of [prepareUpload]: prepare the batch and ask for confirmation. */
|
||||
private suspend fun prepareWavelogUpload(all: List<QsoRecord>, settings: WavelogUploadSettings) {
|
||||
val preview = runCatching {
|
||||
wavelogUploadRepository.prepare(wavelogUploadCandidates(all), settings)
|
||||
}.getOrNull()
|
||||
if (preview == null) {
|
||||
_uiState.update { it.copy(busy = false, message = "Wavelog upload failed to prepare — check the configuration") }
|
||||
return
|
||||
}
|
||||
if (preview.count == 0) {
|
||||
_uiState.update {
|
||||
it.copy(busy = false, message = wavelogIdleSegment(preview) ?: "No pending QSOs to upload to Wavelog")
|
||||
}
|
||||
return
|
||||
}
|
||||
_uiState.update { it.copy(busy = false, wavelogPreview = preview) }
|
||||
}
|
||||
|
||||
fun confirmUpload() {
|
||||
val preview = _uiState.value.preview ?: return
|
||||
viewModelScope.launch {
|
||||
@@ -338,27 +352,28 @@ class LogViewModel(
|
||||
LoTWUploadResult.Unknown -> "Unknown result — will not auto-retry"
|
||||
LoTWUploadResult.ExpiredPreview -> "Preview expired — tap upload again"
|
||||
}
|
||||
// Wavelog piggyback: rides the same confirmation, independent of the LoTW
|
||||
// outcome — a Wavelog failure leaves records unmarked for the next upload.
|
||||
val wavelogPending = _uiState.value.wavelogPending
|
||||
val wavelogMsg = wavelogPending?.let { wl ->
|
||||
if (wl.count > 0) uploadWavelogNow(wl, settingsRepo.wavelogUploadSettings.value)
|
||||
else wavelogIdleSegment(wl)
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
busy = false, preview = null, wavelogPending = null,
|
||||
message = listOfNotNull(msg, wavelogMsg).joinToString("\n")
|
||||
)
|
||||
}
|
||||
_uiState.update { it.copy(busy = false, preview = null, message = msg) }
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissPreview() = _uiState.update { it.copy(preview = null, wavelogPending = null) }
|
||||
/** Confirms the Wavelog-mode batch prepared by [prepareUpload]. */
|
||||
fun confirmWavelogUpload() {
|
||||
val preview = _uiState.value.wavelogPreview ?: return
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(busy = true) }
|
||||
val msg = uploadWavelogNow(preview, settingsRepo.wavelogUploadSettings.value)
|
||||
_uiState.update { it.copy(busy = false, wavelogPreview = null, message = msg) }
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissPreview() = _uiState.update { it.copy(preview = null) }
|
||||
|
||||
fun dismissWavelogPreview() = _uiState.update { it.copy(wavelogPreview = null) }
|
||||
|
||||
/**
|
||||
* Uploads one prepared Wavelog batch (piggyback) and marks what the server
|
||||
* accepted. Returns the operator-facing message segment for this upload.
|
||||
* Uploads one prepared Wavelog batch and marks what the server accepted (stamped
|
||||
* with the station profile the batch went out through). Returns the operator-facing
|
||||
* message for this upload.
|
||||
*/
|
||||
private suspend fun uploadWavelogNow(
|
||||
preview: WavelogUploadPreview,
|
||||
@@ -366,7 +381,7 @@ class LogViewModel(
|
||||
): String = try {
|
||||
val outcome = wavelogUploadRepository.upload(preview, settings)
|
||||
if (outcome is WavelogUploadOutcome.Imported && outcome.markIds.isNotEmpty()) {
|
||||
qsoRepository.markWavelogUploaded(outcome.markIds)
|
||||
qsoRepository.markWavelogUploaded(outcome.markIds, settings.stationId)
|
||||
}
|
||||
wavelogConfirmedSegment(outcome, preview)
|
||||
} catch (_: Exception) {
|
||||
@@ -380,7 +395,7 @@ class LogViewModel(
|
||||
fun abandonForGridFix() {
|
||||
lotwUploadRepository.discardPreview()
|
||||
lastUploadedIds = emptyList()
|
||||
_uiState.update { it.copy(uploadPositionWarning = null, preview = null, wavelogPending = null) }
|
||||
_uiState.update { it.copy(uploadPositionWarning = null, preview = null) }
|
||||
}
|
||||
|
||||
/** Recompute the log-page roaming hint from the freshest station/position data. */
|
||||
|
||||
Reference in new issue
Block a user