The stored sync cursor was 'yyyyMMdd', which LoTW's qso_qslsince silently
ignores (measured live: it falls back to the system-supplied default).
Write a full UTC 'yyyy-MM-dd HH:mm:ss' cursor instead — the documented
format LoTW honors — so incremental pulls are exact (second precision)
and the settings page can show the last successful sync time on the LoTW
card, formatted like the ephemeris update time (prefs_updated_title).
Legacy yyyyMMdd cursors are normalized for the API, the gate and the
display (lotwCursorApi/lotwCursorDate/lotwCursorEpochMs).
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).
Stripe spacing now scales as 44px * 2^(zoom-7) instead of a fixed 44px on
screen: at the map's max zoom (7.0) the stripes keep the established look,
and at lower zooms they shrink with the map, so stripes stay pinned to the
map (same stripes-per-cell at every level) — denser on screen when zoomed
out, sparser when zoomed in.
- 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).
- Pass card header: radar icon button enlarged to 48dp touch target with a
24dp icon, spanning both header rows vertically (name/time + elevations);
the pass info column narrows so the Opposite elevation shifts left.
- RadarDestination no longer auto-returns instantly when the mutual pass has
already ended — the auto-return now only runs while the pass is actually in
progress. Previously the collapsed-card radar shortcut appeared broken
('flash back to Mutual') whenever the computed pass had finished.
- Fakes: add the 4 last-sync bookkeeping members missed by the LoTW commit.
Icon-only radar button at the right end of the collapsed pass header (always
visible, not only when expanded); the pass info shifts left. The button's tap
is consumed and does not toggle the card expansion.
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.
DayCell displayed the count of the newest non-gray slot only; it now sums the
counts of all 12 slots of the day. The tile color still reflects the newest
reported status.
Grid mode is toggled from the map page pill (MapAction.ToggleGridMode writes
stateOfMapGrid); the settings-page switch was a duplicate entry point. Removes
the SwitchRow, the ToggleMapGrid action and its ViewModel branch, and trims
the OtherCard height by one row.
The expanded option rows used fillMaxWidth inside a full-screen Box, which
stretched the selector's dark surface across the whole map. The Column now
sizes to its widest child (IntrinsicSize.Max) so the box is just wide enough
for the label, count and expand arrow.
The pass card time range and the elevation-curve X-axis labels were hardcoded
to the device timezone. MutualUiState now carries isUtc, initialized and kept
in sync with OtherSettings.stateOfUtc (same pattern as PassesViewModel), and
both the card timeFormat and the chart axis use UTC when the setting is on.
Also fixes pre-existing test-fake compile breakage: FakeSettingsRepo built
OtherSettings with positional args missing shouldSeeWhatsNew, and never
implemented the wavelogSettings/lotwSettings/worked-grids members added by
later interface extensions.
Root cause: LocalElevationThresholds was provided inside MainScreen, but
RadarDestination is a root-level sibling entry in NavRoot's NavDisplay, so
it fell outside the CompositionLocal scope and always used the default
15/45 thresholds.
Fix: hoist the CompositionLocalProvider to NavRoot so both the tab content
and the root-level RadarDestination read the user's low/high elevation
thresholds. Also affects upstream (same navigation structure).
WAZ/DXCC award rings are stored with the closing edge implicit (the renderer
close()s single-segment rings). Rings that straddle ±180° are split into
multiple segments, and multi-segment rings must not be close()d (each close()
would draw a spurious chord across the map), so the implicit closing edge
was never drawn -> the shared boundary between WAZ zones 31/32 (165E,-8 ->
180,-12), and the closing edges of Russia's main ring / Fiji / Russia's
Chukotka tip in DXCC, stayed open.
Fix: store every ring explicitly closed (append first point). splitRingAt
Antimeridian then cuts the closing edge at ±180 exactly like any other edge,
so each segment ends on the correct side and the boundary lines render.
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.
Switching the operated-grid selector now re-filters the green worked
cells on the map (the overlay receives the per-grid worked set instead
of the global one, on every recomposition via setGridMode). Previously
only the VUCC progress count changed while the map stayed green for all
operated grids.
The selector gains an 'All (n)' entry at the bottom of the list: it
selects no operated grid (null), showing every worked grid combined and
restoring the global VUCC count.
Maidenhead row R (80-90N) and row A (-90..-80S) vertical lines were drawn
with projectionToY(±90). Web Mercator diverges there: osmdroid's
TileSystemWebMercator yields y01=±Infinity -> Long.MIN/MAX_VALUE pixel
coordinates (verified via JVM test: lat=90 -> -9.2e18, lat=-90 -> +9.2e18).
The canvas cannot rasterize such coordinates, so the polar rows' grid
lines silently disappeared at field zoom (2-char labels).
Fix: clamp the projected latitude to the Mercator limit ±85.0511 in
projectionToY(). The polar rows then project onto the screen edge and
their lines render (the sliver beyond 85.05 is off-screen anyway). Row A
kept its symmetric handling; row R now uses the same path.
- 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.
GridQso gains myGrid (ADIF MY_GRIDSQUARE) captured per QSO during LoTW
parsing (handles alphabetical field order + EOR reset) and persisted as
JSON key 'mg' (absent in pre-myGrid data -> null). Legacy syncs keep the
global VUCC count; one resync unlocks per-grid counting.
Port of upstream PR #252 (wty2019wty): combine otherSettings.stateOfUtc
into initRepository so an existing AOS time window is reapplied in the
newly selected timezone right after toggling UTC, instead of waiting for
the next selection/position change.
- DB v2 migration: entries.ndot column so hasDecayed() actually works after
reload (parsed ndot was dropped on write, making the decay detection dead
code); radios.isCustom column to mark manually imported transceivers.
- updateTransceiversFromFile marks imported radios isCustom=true; remote
updates now deleteManagedRadios() then insert the fresh snapshot, keeping
manual imports (no source can refresh them, so nothing would bring them back).
- Wire MIGRATION_1_2 into MainContainer (fallbackToDestructiveMigration=false
would otherwise crash on upgrade).
- PassesViewModel: use the DEVICE timezone for the natural-day anchor in
findTodaySunRiseSet instead of the display timezone. With UTC enabled the
anchor was UTC midnight = 08:00 local for UTC+8 stations, when the sun is
already up — tripping the white-night guard and rendering '--:--'.
- SatelliteRepo.isAosInRange: follow the UTC display toggle so the pass time
window is interpreted in UTC when UTC mode is on. Previously the list
showed UTC times while the filter silently used local time, off by the
timezone offset (8h for China).
- wapc.json: add name_en for all 34 provinces (Beijing/Guangdong/...)
- waja.json: add name_en for all 47 prefectures (Gifu/Kyoto/...)
- AwardBoundaryOverlay: render English label on non-Chinese UI,
fall back to source name when name_en is absent
- bump version to 4.4.6-ba7opf.10.4 (469)
The radar page is a full-screen destination; the transceiver list had no
bottom inset padding, so on gesture navigation the translucent system bar
floated over the CW decoder's decoded-text output at the end of the list.
Add navigationBars height as LazyColumn contentPadding (no-op on
three-button nav where the window is already resized).
- Zangnan (Arunachal Pradesh): China ring south edge moved from the de-facto
line to the official claim (DataV Tibet south arc); India ring's Arunachal
protrusion removed so the region shows as China (both rings share the new
boundary). Verified: Zangnan points in China ring only, Assam stays India.
- Karakoram corridor / Shaksgam valley (China-controlled since 1963): the
coarse 13-pt west segment (Karakoram Pass -> Afghan/Tajik corner) replaced
with the official Xinjiang south arc (232 pts) — the valley is now inside
the China ring. Aksai Chin was already correct; no other entity deviates
from the official map (SCS islands follow their own DXCC entities).
Field zoom (2-char labels) now draws the station's own 2°x1° square outline
on top of the field grid, so the operator always sees their exact grid.
Polar labels + order-independent LoTW parsing fixes ride along in this release.
parseConfirmedGrids and parseRoamedGrids gated grid fields on propMode == SAT
at the moment the field line was read. LoTW emits ADIF fields alphabetically
(GRIDSQUARE/MY_GRIDSQUARE before PROP_MODE), so the gate was never satisfied
on real reports: the worked set AND the roamed set silently came back empty.
Both parsers now buffer the record and decide at <EOR>, matching the proven
parseConfirmedGridQsos pattern. Regression tests cover the alphabetical order.
- Polar bands (80..90 / -90..-80) extend past the map's latitude limit, so
their label center fell off-screen and the labels never rendered. Labels are
now centered in the VISIBLE part of the cell; fully off-screen cells are
still skipped. Verified with a projection-accurate simulation render: Arctic
band shows IR/JR, Antarctic band shows IA/JA, ±80 lines intact.
- Own-grid bold outline is drawn only at sub-square zoom (zoom >= 6): at field
zoom (2-char labels) the current grid is no longer marked (user request).
The ownGrid-set-every-frame fix stays, so the outline still survives map
refreshes at sub-square zoom.
- Bold outline now uses the same yellow as the regular grid lines
(argb(160,255,224,130)) at 6f stroke — no color change, just thicker.
- Passes top bar matches upstream exactly: left = radios dialog
(ic_radios -> ToggleRadiosDialog), right = passes filter dialog
(ic_filter -> TogglePassesDialog). The calendar button stays only on the
radar page (upstream behavior); removes the now-unreachable
PassesAction.AddToCalendar wiring.
- Version bumped to 4.4.6-ba7opf.10.1.