feat(log): the counterpart grid can be typed
On FM satellites the grid is the exchange - it is what the other station sends you and what you send back. Until now it could only arrive by scraping QRZ, which needs a cookie the operator may not have pasted, and which returns nothing at all for a station with no locator on file. So the field that carries the actual content of an FM contact was the one field the operator could not fill in. It is the second field, optional, six characters, uppercased. A typed grid also skips the QRZ lookup entirely rather than racing it: what the operator heard on the air beats what a web page says, and letting the scrape overwrite it would silently replace good data with a guess. The value is captured before the field clears, so the QSO carries it and the next contact starts empty. WavelogQso.gridsquare already existed for the scraper to fill, so nothing about the stored shape changes and no migration is needed. This was the interaction study's second-ranked conclusion, after the editable time. Both come from the same observation: the screen was built for someone typing during a pass, and the operators it is for are working the radio instead.
This commit is contained in:
1 parent
e2263668ce
commit
cb3ebe7870
3 files changed
+34
No files matched your search
@@ -276,6 +276,8 @@
|
||||
<string name="wavelog_title">日志</string>
|
||||
<string name="wavelog_select_transponder">选择转发器</string>
|
||||
<string name="wavelog_call_hint">对方呼号</string>
|
||||
<string name="wavelog_grid_hint">网格 (可选)</string>
|
||||
<string name="wavelog_grid_help">对方报给你的网格。留空则从 QRZ 查询。</string>
|
||||
<string name="wavelog_mode_hint">模式</string>
|
||||
<string name="wavelog_mode_edit">修改模式</string>
|
||||
<string name="wavelog_time_hint">时间</string>
|
||||
|
||||
@@ -307,6 +307,8 @@
|
||||
<string name="wavelog_title">Log</string>
|
||||
<string name="wavelog_select_transponder">Select transponder</string>
|
||||
<string name="wavelog_call_hint">Callsign</string>
|
||||
<string name="wavelog_grid_hint">Grid (optional)</string>
|
||||
<string name="wavelog_grid_help">What they sent you. Left empty, it is looked up on QRZ.</string>
|
||||
<string name="wavelog_mode_hint">Mode</string>
|
||||
<string name="wavelog_mode_edit">Edit the mode</string>
|
||||
<string name="wavelog_time_hint">Time</string>
|
||||
|
||||
@@ -295,6 +295,10 @@ private fun ExpandedLogInput(
|
||||
// deliberately NOT restored when the user swipes the app away - that is them ending the session.
|
||||
var timeEntry by rememberSaveable { mutableStateOf("") }
|
||||
var timeEditable by rememberSaveable { mutableStateOf(false) }
|
||||
// Typed by the operator. On FM satellites the grid IS the exchange, and until now it could only
|
||||
// arrive by scraping QRZ - which needs a cookie the operator may not have and cannot supply for
|
||||
// a station that has no locator on file.
|
||||
var gridEntry by rememberSaveable { mutableStateOf("") }
|
||||
// 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
|
||||
@@ -333,6 +337,7 @@ private fun ExpandedLogInput(
|
||||
val tx = radio.uplinkLow ?: radio.downlinkLow ?: 0L
|
||||
val rx = radio.downlinkLow ?: radio.uplinkLow ?: 0L
|
||||
val qsoId = UUID.randomUUID().toString()
|
||||
val typedGrid = gridEntry.trim().uppercase()
|
||||
queue.add(
|
||||
WavelogQso(
|
||||
id = qsoId,
|
||||
@@ -347,10 +352,12 @@ private fun ExpandedLogInput(
|
||||
freqRxHz = rx,
|
||||
satName = satelliteName,
|
||||
catnum = satelliteCatnum,
|
||||
gridsquare = typedGrid,
|
||||
sessionId = buildSessionId(satelliteName, aosTimeMs)
|
||||
)
|
||||
)
|
||||
callsign = ""
|
||||
gridEntry = ""
|
||||
workedThisSession = workedThisSession + call
|
||||
onSaved()
|
||||
showToast(
|
||||
@@ -366,6 +373,9 @@ private fun ExpandedLogInput(
|
||||
// Grid backfill. The view model owns the cookie and the request; this used to read
|
||||
// SharedPreferences through LocalContext right here, inside composition. Failures now say
|
||||
// something: an expired cookie was indistinguishable from a station with no grid filed.
|
||||
// A typed grid is better evidence than a scrape, so the lookup is skipped entirely rather
|
||||
// than allowed to overwrite it.
|
||||
if (typedGrid.isNotBlank()) return
|
||||
onLookupGrid(call) { outcome ->
|
||||
when (outcome) {
|
||||
is QrzGrid.Found -> {
|
||||
@@ -441,6 +451,26 @@ private fun ExpandedLogInput(
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
// The counterpart grid, optional and second. On FM satellites this is the exchange, and it
|
||||
// could previously only arrive by scraping QRZ - which needs a cookie, and returns nothing
|
||||
// for a station with no locator on file. Typed here it also skips the lookup entirely,
|
||||
// because what the operator heard beats what a web page says.
|
||||
OutlinedTextField(
|
||||
value = gridEntry,
|
||||
onValueChange = { gridEntry = it.take(6).uppercase() },
|
||||
label = { Text(stringResource(id = R.string.wavelog_grid_hint)) },
|
||||
supportingText = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wavelog_grid_help),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { submit() }),
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
// The mode comes from the transponder, so it is shown rather than typed - one less field
|
||||
// during a pass that lasts eight minutes. Tapping it reveals the field, because a
|
||||
// transponder record can be wrong and the operator has to be able to say so.
|
||||
|
||||
Reference in new issue
Block a user