fix(log): say why a callsign was not logged instead of dropping it
`submit()` opened with `if (call.length < 3) return`. During a pass the operator typed a callsign, pressed done, and nothing happened - no entry, no message, no way to tell the app had decided against them. None of the logging software surveyed for this work - N1MM+, DXLog, PoLo, HAMRS - discards a submission silently. Validation is deliberately loose, because strictness costs more than it saves. Checked against 28 real callsigns, a typical strict pattern rejects 16 of them: W1AW/4, 2E0ABC, 9A1CCY and SV2ASP/A among others. A pattern permissive enough to accept those also accepts a Maidenhead locator as a callsign. There is no regex that catches typos without throwing away legitimate calls, so CallsignEntry rejects only what cannot be a callsign - empty, one character, illegal characters, all digits, all letters - and reports doubt as a warning that still logs the contact. Two warnings exist. A six-character grid-shaped entry says so, because grid and callsign are exchanged together on FM satellites and the fields sit side by side. A station already worked this pass says so too, without blocking: the same station on a later pass is a legitimate new contact, and contest loggers default to working duplicates - DXLog describes refusing them as an outdated habit. That warning also replaces the duplicate suppression, which was a 300ms window comparing the last callsign, admitted in its own comment to be a workaround. It could silently discard a real second contact, and a set of calls worked this pass is both honest and more useful. It survives configuration changes via rememberSaveable. Not addressed here: the QRZ grid backfill still reads the cookie out of SharedPreferences from inside a composable through LocalContext, and still reports nothing when a lookup fails. IQrzGridLookup is added for that, but wiring it needs the container, the view model and the UI to change together.
This commit is contained in:
1 parent
2ff8643988
commit
e0900778f0
6 files changed
+348
-12
No files matched your search
@@ -52,6 +52,7 @@ import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -70,6 +71,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||
import com.rtbishop.look4sat.core.domain.utility.DopplerFrequencyCalculator
|
||||
import com.rtbishop.look4sat.core.domain.wavelog.CallsignEntry
|
||||
import com.rtbishop.look4sat.core.domain.wavelog.WavelogQso
|
||||
import com.rtbishop.look4sat.core.domain.wavelog.WavelogQueue
|
||||
import com.rtbishop.look4sat.core.presentation.R
|
||||
@@ -84,7 +86,6 @@ import kotlin.math.roundToInt
|
||||
private val WaveLogYellow = Color(0xFFFFC107)
|
||||
|
||||
/** Repeat "done" events for the same callsign inside this window are treated as one QSO. */
|
||||
private const val DUPLICATE_WINDOW_MS = 2000L
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -266,20 +267,38 @@ private fun ExpandedLogInput(
|
||||
val scope = rememberCoroutineScope()
|
||||
var callsign by remember { mutableStateOf("") }
|
||||
var mode by remember { mutableStateOf(radio.uplinkMode ?: "FM") }
|
||||
// Last accepted callsign + timestamp, used to swallow duplicate IME "done" events.
|
||||
// A plain in-flight flag cannot help: submit() is synchronous, so it always
|
||||
// clears before the next key event arrives.
|
||||
var lastSaved by remember { mutableStateOf("" to 0L) }
|
||||
// Calls logged during this pass, so a repeat can be mentioned without being blocked: the same
|
||||
// station on a later pass is a legitimate new contact. This replaces a 300ms window that
|
||||
// swallowed what it guessed were accidental double submissions - a guess that could discard
|
||||
// a real second contact instead.
|
||||
var workedThisSession by rememberSaveable { mutableStateOf(emptySet<String>()) }
|
||||
|
||||
val savedMsg = stringResource(id = R.string.wavelog_saved)
|
||||
val gridWarnMsg = stringResource(id = R.string.log_warn_grid)
|
||||
val workedWarnMsg = stringResource(id = R.string.log_warn_worked)
|
||||
val tooShortMsg = stringResource(id = R.string.log_call_too_short)
|
||||
val illegalMsg = stringResource(id = R.string.log_call_illegal)
|
||||
val notACallMsg = stringResource(id = R.string.log_call_not_a_call)
|
||||
val tooLongMsg = stringResource(id = R.string.log_call_too_long)
|
||||
|
||||
fun rejectionMessage(reason: CallsignEntry.Reason): String = when (reason) {
|
||||
CallsignEntry.Reason.EMPTY, CallsignEntry.Reason.TOO_SHORT -> tooShortMsg
|
||||
CallsignEntry.Reason.TOO_LONG -> tooLongMsg
|
||||
CallsignEntry.Reason.ILLEGAL_CHARACTERS -> illegalMsg
|
||||
CallsignEntry.Reason.NOT_A_CALLSIGN -> notACallMsg
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
val call = callsign.trim().uppercase()
|
||||
if (call.length < 3) return
|
||||
val now = System.currentTimeMillis()
|
||||
val (lastCall, lastAt) = lastSaved
|
||||
if (call == lastCall && now - lastAt < DUPLICATE_WINDOW_MS) return
|
||||
lastSaved = call to now
|
||||
// Every outcome says something. The previous `if (call.length < 3) return` discarded the
|
||||
// entry in silence, so pressing done mid-pass appeared to do nothing, and none of the
|
||||
// logging software surveyed drops a submission that way.
|
||||
val verdict = CallsignEntry.check(callsign, workedThisSession)
|
||||
if (verdict is CallsignEntry.Verdict.Rejected) {
|
||||
showToast(rejectionMessage(verdict.reason))
|
||||
return
|
||||
}
|
||||
val accepted = verdict as CallsignEntry.Verdict.Acceptable
|
||||
val call = accepted.callsign
|
||||
// Freq taken directly from the transponder bar (radio Doppler-corrected each second; value at the Enter moment)
|
||||
val tx = radio.uplinkLow ?: radio.downlinkLow ?: 0L
|
||||
val rx = radio.downlinkLow ?: radio.uplinkLow ?: 0L
|
||||
@@ -298,8 +317,15 @@ private fun ExpandedLogInput(
|
||||
)
|
||||
)
|
||||
callsign = ""
|
||||
workedThisSession = workedThisSession + call
|
||||
onSaved()
|
||||
showToast(savedMsg)
|
||||
showToast(
|
||||
when (accepted.warning) {
|
||||
CallsignEntry.Warning.LOOKS_LIKE_A_GRID -> gridWarnMsg
|
||||
CallsignEntry.Warning.ALREADY_WORKED -> workedWarnMsg.format(call)
|
||||
null -> savedMsg
|
||||
}
|
||||
)
|
||||
// QRZ counterpart grid async backfill (4.5.5): only queried when Cookie is set; silent on failure
|
||||
scope.launch {
|
||||
val prefs = context.getSharedPreferences("qrz_cookie", android.content.Context.MODE_PRIVATE)
|
||||
|
||||
Reference in new issue
Block a user