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.
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)
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
- 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_*)
- Grid-QSO dialog gains a Match action (radio-tower icon + label) in its
header; tapping it navigates to the match page pre-filled with the tapped
grid as the opposite station and a 24h time range, auto-starts the query,
and scrolls the page straight to the time-range card.
- Every grid is now tappable (previously only worked grids opened the
dialog); unworked grids show an empty QSO list instead of a hint.
- pass_whatsnew_message rewritten as a single line with escaped \n bullets:
real newlines in the value are invalid for aapt2 ('not a valid string').
- Escape the apostrophe in "map's"; mark grid_qso_calls_count
formatted=false (two %d + '·' triggered multiple-substitutions).
- values-si: remove stray comma after map_altitude </string>.
All were latent resource errors surfaced by the forced recompile.
- Pass history (Iu-yang1 fork): PassesSettings.hoursBefore look-back window;
second 'Time before' slider in the pass filter; completed passes stay
listed (progress clamped to [0,1]) in the history window.
- Compass calibration (Iu-yang1 fork): OtherSettings.compassOffsetDegrees,
CompassAccuracy from the magnetic sensor, calibration dialog with
figure-eight accuracy meter, corrected heading and manual offset field;
RadarViewModel applies the offset.
- What's New dialog now shows the cumulative 4.4.6->4.4.7 changelog and
follows the system language (values/ + values-zh/).
- pass_filter_history_hours localized in es/ru/si/tr/uk.
Add a 'first call' pill toggle below the grid-mode switch (map top-right,
visible only in grid mode + VUCC award). When on, worked (green) cells are
labelled with the first callsign worked in that grid (earliest confirmed
QSO by time) instead of the Maidenhead code; non-worked cells get no label
at all. Persisted via OtherSettings.stateOfMapFirstCall (full MVI chain).
The feature is VUCC-only: All and other award overlays never draw it.
Mirrors the ephemeris auto-update check: on every app start, if LoTW
credentials are configured, the new 'auto-sync LoTW grids' toggle is on,
at least one manual sync was done before, and today has not synced yet
(ARRL rate limit, ~once a day), pull confirmations since the last sync
and merge them. Same callsign -> incremental merge; callsign change ->
full replace. Manual dialog and automatic path share the orchestration
(mode resolution, gate, merge/persist) moved to core:domain
LoTWGridSync.kt; LoTWSyncMode moved to core:domain. 8 unit tests.
- Cancel: the dialog's cancel/back while syncing now aborts the download job
(cooperative cancellation in the stream reader, CancellationException
propagated, nothing written to storage). An accidental full sync can be
stopped immediately.
- Progress: the size estimate is conservative (900 B/record vs measured 720)
so the bar never hits 100% before the download truly ends; storage writes
moved to the IO dispatcher (multi-MB per-grid QSO JSON used to freeze the
main thread after download); when the bar caps, the UI switches to
'Processing downloaded data…' instead of looking stuck.
- Removed the outdated '3-10 seconds' hint (misleading for large accounts).
- SharedDialog title gets start padding when only a cancel button is shown
(was flush against it); dialogs with both buttons unchanged.
- LoTW dialog: full-sync button bottom-left, incremental-merge bottom-right
(SpaceBetween instead of side-by-side).
Two explicit sync buttons in the LoTW dialog:
- Full sync: qso_qslsince=2000-01-01, fresh report replaces all stored data.
- Incremental merge: since=last successful sync date, merged into the stored
set (grids union, QSOs deduped by call + time). First sync / callsign change
automatically falls back to full replace so no stale grids from another
account linger.
Progress & estimates:
- Report body is now streamed; the header's <APP_LoTW_NUMREC> gives the real
QSL count, total size is estimated as NUMREC * 720 B (measured), and the UI
shows a determinate bar with 'N QSOs, ~X s remaining' once known.
- Pre-sync hint shows the measured rate (~8 s per 100 QSOs).
- readTimeout raised to 600 s (stall timeout, not a total cap — huge accounts
legitimately take 30+ min at ARRL's ~10 KB/s stream rate); connectTimeout
raised to 60 s (a busy server took 36.5 s to accept a connection).
Also: ISettingsRepo gains last-sync date/callsign bookkeeping, and the mutual
test fake implements the new members.
Android plurals have no 'one' category in Chinese (zh plural rules only
contain 'other'), so a single QSO rendered as '1 QSOs' on Chinese-locale
devices. Replace the plurals resource with explicit singular/plural
strings selected in code.
- QSO dialog: each callsign row shows 'n QSO'/'n QSOs' (plurals) centered
between callsign and date, styled like the date.
- Grid mode + VUCC filter: top-left selector picks the operated grid the
VUCC count is computed from (defaults to the most-worked grid), with a
collapsible list of all operated grids; arrow style matches the QSO
dialog rows.
- wapc/dxcc assets: add Hong Kong & Macau labels (forceLabel) and MO boundary in dxcc
- AwardBoundaryOverlay: rings split at antimeridian no longer close() per segment — removes spurious Russia cross-continent chord
- AwardCalculator: Macau DXCC entity 330 -> 152 (+test)
- Settings LoTW dialog: add sync duration hint (zh/en)
- Grid-mode top bar replaced with award filter chips (All/DXCC/VUCC/
WAPC/WAJA/WAZ/WAS) showing live progress (count/target)
- New AwardBoundaryOverlay renders real administrative boundaries
(provinces/prefectures/CQ zones/states/countries) with the VUCC visual
language: yellow boundary lines, green fill for worked regions, labels
- Boundary assets packaged from public GeoJSON (DataV China, Japan land,
HB9HIL CQ zones, Natural Earth states/countries) with ADIF match keys:
WAPC province codes, WAJA ADIF prefecture numbers, US state abbrs,
ARRL DXCC entity codes
- GridQso extended with dxcc/country/cqz/state parsed from LoTW report
(qso_qsldetail); AwardCalculator derives six-award progress; STATE is
interpreted per DXCC entity (ADIF 'STATE depends on DXCC')
- Grid-mode toggle pill floated at map top-right (Grid mode / Satellite mode)
- New tests: AwardCalculatorTest (9 cases), LoTW award-field parsing (3 cases)
- LoTW sync now stores per-QSO detail (call/time/sat/mode/bands) grouped by
gridsquare (PROP_MODE=SAT only), single JSON blob in SharedPreferences
- GridQso model derives band label (V/U, U/V, V/A...) from BAND_RX/BAND
- Tap detection via osmdroid MapEventsOverlay; selected grid outlined green
- Centered dialog: calls sorted by first-contact time, first-QSO summary,
expandable call rows list subsequent QSOs in that grid; tap outside closes
Add a full-screen update checker accessible from a button at the
bottom of the settings page. It queries the fork's GitHub latest
release and can download + install the APK in place.
- UpdateRepository (core/data): fetch releases/latest via GitHub API,
parse tag/description/APK asset, stream APK download to cacheDir
- VersionComparator (core/domain): compare 4.4.6-ba7opf.N style tags
(base version first, then build number; v-prefix tolerant) + tests
- IMainContainer/MainContainer: inject updateRepo
- UpdateCheckerScreen (feature/settings): shows current version,
checks for updates, displays latest version + release notes, and a
Download & install button; FileProvider hands the APK to the
system installer
- SettingsScreen: bottom entry button, full-screen page toggle
- AndroidManifest: REQUEST_INSTALL_PACKAGES permission + FileProvider
- i18n strings (en/zh)
Port the upstream drag-reorder pattern (dragHandle/draggedVisual/
autoScroll/DragRowState) so custom data source rows can be reordered
by long-pressing the handle icon, replacing the old move-up button.
- Add 40dp drag-handle box at row start with long-press gesture
- Live reorder while dragging (neighbour squeeze + spring settle)
- Edge auto-scroll during drag
- Keep per-source enabled checkbox + HTTP status label
This file also carries the #242 data-source dialog UI (enabled
checkbox, status code label, restore-default button) which shares
the same sourceSection function and cannot be cleanly split.
When adjusting RX in passband mode (via +/- buttons or slider), the old
code used TransponderMapper.mapDownlinkToUplink (pure passband mapping
without Doppler compensation), producing ~10kHz error at LEO velocities.
Fix: use DopplerFrequencyCalculator.computeUplinkFromDownlinkWithOffset
for the full TX-from-RX round-trip, matching the RX→PASSBAND button
onClick logic. Also fixed frame mismatch: position now uses txLow/txRange
(Doppler-shifted frame) consistently.
- Adjust ic_satellite_alt translateY from -226.13 to -230.5 so icon
is centered vertically (not clipped at bottom)
- Replace ic_match with ic_radio_tower (MDI) for Mutual page
Port upstream PR #234 to fork main:
- New feature:status module (SatStatusScreen, SatStatusViewModel)
- New AmSatRepository + IAmSatRepository + SatStatus models
- RemoteSource: AMSAT API methods (getAmSatCatalog, getAmSatReports)
- MainContainer/IMainContainer: DI injection
- MainTheme: AMSAT status colors (tertiary slots)
- Chinese translations added for all AMSAT strings
- 26 unit tests passing
Note: navigation entry not included — upstream PR #234 intentionally
left it out pending UI restructuring. Add AMSAT to your nav bar or
menu as desired.
Port satlover.de dual-station pass matching feature:
- New feature/mutual module with mutual pass data model
- MutualViewModel: compute overlapping passes for two stations
- ElevationCurveChart: Canvas-based dual elevation curve with drag
- MutualScreen: input form + results with expandable cards
- Navigation: add Mutual tab to bottom navigation bar
- i18n: add Chinese/English strings for new feature
Use MaterialTheme.colorScheme.primary for satellite name
text in all three lists (Components, SatellitesScreen,
PassesScreen), matching the color that was previously
used for the catalog number.