fix(wavelog,radar): 4.5.2 second overwrite - v1 API support, error dialog with copy, frequency sync

Background: user's WaveLog server has NO v2 API (all /api/v2/* return
404; /api/qso v1 works - verified with curl). First fix only tried v2
paths, so uploads still failed with 404. Also: Log tab frequencies did
not match the Doppler panel, linear transponders showed a single
frequency instead of the passband range, and errors were toast-only
(no copy).

Changes:
- WaveLogApi: full v1 support with auto fallback
  - v2 first (Bearer header + JSON fields), on 404 fall back to v1
    (key inside JSON body + ADIF string) - both with and without
    index.php prefix
  - test connection: v2 GET api/v2/token -> v1 POST
    api/get_contacts_adif (validates key + station id)
  - station gridsquare is v2-only; on v1 the uploader falls back to
    the user's QTH grid (grid check skipped/equal)
  - v1 ADIF: call/band=SAT/mode/freq+freq_rx (MHz)/qso_date/time_on
    (UTC, compact)/gridsquare(4)/sat_name/prop_mode=SAT, byte-length
    field prefixes
  - 409 duplicate counts as success in both versions
- Error dialog with copy: test/upload failures now open an AlertDialog
  with the full error (incl. actual URL + HTTP code), Copy button
  (ClipboardManager) and Cancel; uploader collects the first failure
  message
- Log tab frequency sync: LogTab receives txBaseFrequencyHz from the
  radar page (the tuned frequency shown in the transceiver panel);
  RX is computed through the same Doppler mapping as the Doppler
  panel; linear transponders show the full uplink/downlink range
  (Doppler-corrected low-high) instead of a single frequency
- Restored uploadWavelogQueue (lost in an earlier patch)

Verified: check_strings.py OK (8 files, 452/4.5.2);
:app:compileDebugKotlin BUILD SUCCESSFUL.
This commit is contained in:
mckero committed 2026-08-04 11:47:06 +00:00
1 parent 5351afe508
commit 9d437a90c9
10 files changed
+250 -147

No files matched your search

@@ -44,6 +44,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.grid.GridCells
import android.content.Context
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.AlertDialog
@@ -144,6 +145,35 @@ fun SettingsDestination() {
}
)
}
// WaveLog 错误详情弹窗(可一键复制)
val wavelogError = viewModel.wavelogError
if (wavelogError != null) {
AlertDialog(
onDismissRequest = { viewModel.wavelogError = null },
title = { Text(text = stringResource(id = R.string.wavelog_title)) },
text = {
Text(
text = wavelogError,
fontSize = 13.sp
)
},
confirmButton = {
TextButton(onClick = {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
clipboard.setPrimaryClip(android.content.ClipData.newPlainText("WaveLog error", wavelogError))
viewModel.wavelogError = null
}) {
Text(text = stringResource(id = R.string.wavelog_copy))
}
},
dismissButton = {
TextButton(onClick = { viewModel.wavelogError = null }) {
Text(text = stringResource(id = R.string.btn_cancel))
}
}
)
}
}
@Composable
@@ -232,32 +232,39 @@ class SettingsViewModel(
// region WaveLog(4.5.2)
private fun testWavelogConnection() {
viewModelScope.launch {
val s = settingsRepo.otherSettings.value
val result = WaveLogApi.testToken(s.wavelogUrl, s.wavelogApiKey)
showToast(
when (result) {
is WavelogResult.Success -> "WaveLog: 连接成功"
is WavelogResult.Failure -> "WaveLog: ${result.message}"
}
)
}
}
// 由 SettingsScreen 注入的共享 uploader(容器级单例)
var pendingUploader: WavelogUploader? = null
// 网格不一致确认弹窗状态(UI 观察后弹 AlertDialog)
var gridConfirm by mutableStateOf<GridConfirmData?>(null)
// WaveLog 错误详情弹窗(UI 观察后弹 AlertDialog, 可一键复制)
var wavelogError by mutableStateOf<String?>(null)
data class GridConfirmData(val stationGrid: String, val userGrid: String)
private fun testWavelogConnection() {
viewModelScope.launch {
val s = settingsRepo.otherSettings.value
val result = WaveLogApi.testToken(s.wavelogUrl, s.wavelogApiKey, s.wavelogStationId)
when (result) {
is WavelogResult.Success -> showToast("WaveLog: ${result.message}")
is WavelogResult.Failure -> wavelogError = result.message
}
}
}
private fun uploadWavelogQueue() {
viewModelScope.launch {
val result = pendingUploader?.uploadQueue()
when (result) {
is UploadOutcome.Done -> showToast("WaveLog: ${result.message}")
is UploadOutcome.Done -> {
if (result.failedCount > 0 && result.firstError.isNotBlank()) {
wavelogError = result.firstError
} else {
showToast("WaveLog: ${result.message}")
}
}
is UploadOutcome.NeedConfirm -> {
gridConfirm = GridConfirmData(result.stationGrid, result.userGrid)
}