The LoTW upload card read uiState.lotwCertificate which was only
populated when the config dialog was opened (LoadLoTWUploadStatus).
On first entry to the settings screen it stayed null, showing
'certificate not imported' even though a certificate was stored.
Load certificate + station once in init so the card is correct
from the first frame.
The import previously swallowed all exceptions silently. Now the upload
config dialog shows a specific message for: wrong password, expired
certificate, invalid file, and unknown failures.
- displayMode no longer trusts a stale submode for non-MFSK modes: FM/CW/SSB
records (including ones persisted with the old default submode=FT4) now show
their real mode; only MODE=MFSK uses the FT4 submode. QsoRecord defaults
changed mode=MFSK->FM, submode=FT4->''.
- LoTW report bridge sets submode=FT4 for MODE=MFSK so confirmations still
match local FT4 records.
- Logbook and LoTW upload config dialogs now use SharedDialog (bottom-sheet),
matching the LoTW confirmed-grids dialog style.
The Choose .p12 button was gated on a non-blank password, which forced users
to type the password before selecting the file. Now the file picker is always
available; after selection the filename is shown, then the password field and
an Import button appear.
- Port TQ8 signing/upload pipeline (core/data/lotw: LoTWConfig, LoTWKeyMaterial,
LoTWSigner, LoTWStorage, LoTWUploadRepository) with bundled ARRL config.tq6
- Multi-grid station locations: primary grid in GRIDSQUARE + full set in
MY_VUCC_GRIDS (unsigned tSTATION extra, IOTA precedent); signer matches
myGrid against the whole grid set
- Satellite-name normalization: SatNOGS names ("SO-50 (SaudiOSCAR 50)") resolve
to official LoTW names by exact-then-substring match
- Logbook storage: separate QsoDatabase (version 1, no migration on existing
installs), QsoRepository with dedupe/confirmation merge; LoTW sync (manual +
auto) feeds confirmations into the logbook and marks local uploads confirmed
- Radar Log tab: single-callsign recording, per-satellite mode preset
(CW/SSB/FT4), English social post generation (copy/share), LoTW upload with
one-shot preview confirmation + receipts ledger
- Settings: 日志本 section (records + confirmation state, delete on tap) above
LoTW config; LoTW upload config (TrustedQSL .p12 import + password, station
grid list incl. IOTA/CQZ/ITUZ)
- i18n strings (en/zh)
A LoTW station location (台址) can span several grid squares: the report
emits <MY_VUCC_GRIDS> (comma-separated, MY_GRIDSQUARE often absent) on
those records, but the app parsed only <MY_GRIDSQUARE>, so every extra
grid of a multi-grid 台址 silently vanished from both the blue roamed
stripes and the operated-grid (VUCC) selector.
- GridQso gains myGrids (full own-grid set), myCallsign and stationKey
(MY_* station snapshot); myGrid stays as the first grid for back-compat.
- parseConfirmedGridQsos parses MY_VUCC_GRIDS + the MY_* snapshot fields;
parseRoamedGrids parses MY_VUCC_GRIDS too and uppercases (lowercase
grids never matched the overlay labels).
- SettingsRepo persists mgs/mc/sk; old data falls back to single-grid sets.
- MapScreen groups the selector's 台址名单 by the 台址's full grid set:
a multi-grid 台址 is one entry listing all its grids (e.g. 'OM60,PM01'),
and selecting it fills every worked grid worked from any of them.
Identical grid sets merge (VUCC-equivalent), so partial MY_* snapshots
can't split one 台址 into duplicate entries. Own grid still never
striped.
Verified against real reports: BH6RJD (807 SAT QSOs) yields 7 台址
including 4 multi-grid ones; BA7OPF's own data stays a single 'OL62'
entry. 28 LoTW parser tests pass (5 new).
User correction: AO-123 is ASRTU-1 (NORAD 61781, launched 2024-11-04,
in orbit), not Fox-1D (43137, re-entered 2024-02). Update the hardcoded
FM set and its tests to include ASRTU-1.
Root cause of the recurring empty AMSAT Live filters: OkHttp defaults to a
10s read timeout and amsat.org's live pages take 7-10s+ from many
networks — every sync timed out, fmNames/linearNames stayed empty, and
(with no previous list, e.g. right after clearing data) the filters were
written as EMPTY. The stale pre-whitelist FM list survived only because
older syncs had occasionally succeeded and the failure path preserved it.
Fix:
1. OkHttp timeouts raised (connect 20s / read 30s / write 30s / call 60s)
in MainContainer.provideRemoteSource so slow AMSAT pages actually load.
2. updateAmSatLiveLists gains a last-resort fallback: when the AMSAT page
is down AND there is no previous list to clean, derive FM/Linear from
the local transceivers (FM / SSB,CW,USB,LSB modes) intersected with
the amateur whitelist, so a network timeout never wipes the filters.
ISS module aliases stay excluded by the whitelist intersection.
Regression test: page fails + empty previous list -> fallback yields
{25544} (26700 excluded by whitelist).
resolvePerName crashed with NoSuchElementException (all.first()) when an
AMSAT live-page name had no matching local entry (TEVEL2-1..9, RS95S, any
brand-new sat not yet in the local TLE). The exception was swallowed by
updateAmSatLiveLists' runCatching, so the FM/Linear lists were NEVER
rewritten — they stayed at the stale pre-whitelist snapshot containing
all five ISS modules, which is why ISS kept showing as DESTINY despite the
15.4/15.5/15.6 disambiguation fixes.
Fix: return an empty set for names with no local match (correct skip
semantics) so the rest of the page still resolves and the ISS module
collapse runs. Regression test covers the real AMSAT page mix (ISS +
unknown names).
Root cause of "All filter is empty": 'All' is a real TLE source type
(CelesTrak active group) in satelliteDataUrls, but setSatelliteTypeIds
had an upstream-legacy 'if (type == "All") return' that silently dropped
its ids on every sync. Selecting 'All' in the type dialog then resolved
to an empty catnum set -> empty list, while entries stayed populated
(which is why satellite counts looked normal).
Also fix getTypesList: removeAt(0) removed the alphabetically-first real
source ('ARISS') instead of the intended placeholder; now excludes only
'Other' (empty URL), so all real source types incl. 'All'/'ARISS' are
selectable.
Tests: remote update persists All ids; All type resolves to persisted
CelesTrak active ids; getTypesList excludes only Other.
Two independent defects kept ISS (DESTINY) visible in the satellite-page
AMSAT Live FM filter even after installing the fix and syncing data:
1. UI-layer staleness (SelectionRepo): resolveTypeIds ran only when the
filter type changed and snapshotted the catnum set. A background data
sync rewriting the FM/Linear lists never re-triggered resolution, so
the UI kept filtering with the pre-sync (dirty) set until restart or a
type toggle. ISettingsRepo now exposes a monotonic amSatListsVersion
StateFlow, bumped by SettingsRepo.setAmSatCatnums; SelectionRepo
combines it with currentTypes so every rewrite re-resolves instantly.
2. Data-layer staleness (DatabaseRepo): when the AMSAT FM page fetch
failed, the previous list was kept verbatim — including pre-whitelist
era lists containing all five ISS module entries. The stale list is now
re-disambiguated: previous catnums are looked up by local name and run
through the same whitelist / smallest-catnum collapse, so ISS modules
always collapse to the primary ZARYA (25544).
- Failed whitelist fetch (raw.githubusercontent.com unreachable on some
networks) no longer wipes the persisted whitelist or admits every ISS
module alias: fall back to the persisted whitelist, and only persist
when a stream was actually fetched.
- Multi-match collapse without any whitelist now keeps the primary entry
(smallest catnum, ISS ZARYA=25544) instead of every alias (DESTINY etc.).
- Failed FM/Linear page fetch keeps the previous lists instead of
clearing the filters.
- Amateur whitelist gets a jsDelivr CDN mirror as fallback source.
- Resolve ISS station modules (ZARYA/UNITY/ZVEZDA/DESTINY/NAUKA) to the
primary 25544 instead of whichever module appears first in the TLE source
- Match OSCAR designators to local names spelled 'OSCAR N'/'AMSAT-OSCAR N'
(AO-7 -> OSCAR 7) so Linear lists keep AO-7 on SatNOGS-sourced data
- SSTV virtual type now uses the transceiver service class (Amateur only)
plus launcher-debris name exclusion, dropping retired weather birds
(TIROS) and rocket bodies (ARIANE 6 R/B) while keeping ISS/CSS/TEIKYOSAT
- Persist the AMSAT active-frequency whitelist (palewire mirror) on sync;
used only to disambiguate multi-match names, never as a hard filter
- matchesAmSatName: exact token match instead of substring contains,
so 'ISS' no longer matches AISSAT-1 and 'AO-7' no longer matches
AO-73 (FUNCUBE-1); resolves missing ISS in FM and missing AO-7 in
Linear lists
- resolveTypeIds: intersect SSTV catnums with current in-orbit TLE set,
excluding retired satellites (e.g. TIROS) whose radios are tagged SSTV
- add regression tests for token matching
Two correctness fixes for the FM/Linear/SSTV filters:
1. Satellite type picker: virtual transponder types (AMSAT Live FM,
AMSAT Live Linear, Live SSTV) are now mutually exclusive with the
regular TLE-source types - ticking a virtual type clears Amateur/
CubeSat/etc and vice versa, so selecting 'AMSAT Live FM' no longer
unions in hundreds of non-transponder satellites. Virtual types
still combine with each other. Also: an empty virtual-type resolve
now yields an empty list instead of falling back to 'show all'.
2. Mutual-match fallback: when the AMSAT lists are empty (first run /
sync failure) the DB mode filter used downlinkMode alone, so any
downlink-only beacon sharing a mode label was misclassified
(ISS/IO-86 CW beacons into Linear, FO-29's FM Digitalker into FM).
New getIdsWithModesAndUplink() requires uplinkLow IS NOT NULL,
matching real transponders only. SSTV filter keeps the old query
(SSTV sats are often downlink-only).
Shared virtual-type names moved to Sources.virtualTypeNames.
The satellite selection screen's 'Select satellite type' dialog now lists
three virtual types first (AMSAT Live FM, AMSAT Live Linear, Live SSTV)
before the TLE-source types. Selecting one filters the satellite list to
that transponder/activity set; multiple selections union. 'All' is kept.
- SelectionRepo.getTypesList: virtual types prepended (All preserved)
- SelectionRepo.resolveTypeIds: FM/Linear -> AMSAT catnum sets,
Live SSTV -> mode=SSTV radios; other types via persisted IDs
- Tests: virtual-type filtering, union, clear, list-order assertion
Mutual match page gains FM/Linear transponder-type filters. The
authoritative source is AMSAT's human-maintained Live FM/Linear
Satellites pages, fetched during data sync and stored as catnum sets;
filters resolve to those sets (falling back to DB mode filter when
empty), and unchecking both shows a prompt instead of querying.
- DataParser.parseAmSatLivePage: table rows to satellite names,
TEVEL2-1 thru TEVEL2-9 range expansion, bracket-alias matching
- SettingsRepo: AMSAT FM/Linear catnum sets persisted in prefs
- DatabaseRepo: fetch AMSAT pages in updateFromRemote, best-effort
(failure keeps previous list, never blocks the update)
- getIdsWithModes: AND isAlive = 1
- MutualScreen: FilterChip row inside MatchSearchCard
- Tests: parser (4) + viewmodel (3) + repo fakes
- MarkedStation storage upgraded to an ordered list per grid
(Map<String, List<MarkedStation>>); JSON array shape with legacy
v14.1 single-object migration on read
- MapAction: SetMarkedStation appends (duplicate callsign ignored),
RemoveMarkedStation deletes by callsign, PinMarkedStation moves a
mark to the top of the list
- MaidenheadGridOverlay: red fill for any marked grid unchanged;
first-call label mode shows the FIRST (pinned) marked callsign
- MapScreen: one row per mark (pale-yellow callsign), pin icon
(冖 + stroke ↑, drawn in-app) left of the Marked label — shown only
when the grid has 2+ marks; pinned row's icon grey, others pale
yellow; delete icon per row
- New ic_pin.xml vector drawable; strings grid_mark_pin
- MapScreen: mark-a-station callsign input uppercases as you type
- MutualViewModel: read passesSettings.hoursBefore, shift the mutual
match query start to now-hoursBefore so historical passes are
included when the main Passes page has a lookback window set;
endTime stays at now+hoursAhead
- New MarkedStation model + ISettingsRepo/SettingsRepo persistence
(single JSON key markedGridStations, org.json only)
- MapState/MapViewModel: markedGrids state, Set/RemoveMarkedStation
actions; marks auto-clear once the grid becomes worked
- MaidenheadGridOverlay: red fill (alpha 90, same as green worked
fill) for marked grids at both zoom levels; first-call label mode
shows the marked callsign in pale yellow on red cells
- MapScreen: unworked-grid dialog gains a 'mark a station you want
to contact' button + callsign input dialog; marked row shows red
callsign, 'Marked' in the count column, marking date, delete icon
- i18n: values/ + values-zh/ (grid_mark_*)