Commit Graph
21 Commits
Author SHA1 Message Date
mckero 96bbb022e8 fix(cw): follow the transcript reliably, and keep the AMSAT grid dense
Two corrections to 10c415fa and 0889a3bd, keeping what those got right and
undoing what they cost.

The transcript now follows new text through an explicit follow flag rather than
comparing scroll position against maxValue. maxValue is written during layout,
after the composition that would read it, so the comparison tested the previous
frame's height: following fell progressively short of the true bottom and, once
the gap passed the slack, latched the operator out of follow-mode until they hit
the exact end. Scrolling away still stops it, which is the point.

The AMSAT day cell goes back to 28 dp. Raising it to 48 dp for the minimum touch
target measured a 71% increase in row pitch - 14 satellites per screen down to 8
on a 6.1" phone - and comparing many satellites at a glance is what that page is
for. Compose cannot extend a touch target past the layout bounds, so this is a
choice rather than a fix; 28 dp is also what shipped before, so the regression
was mine. The contentDescription added alongside it stays, since it costs nothing.
2026-08-23 09:46:36 +00:00
mckero 10c415fabd feat(cw): draw the whole audio band so an out-of-window tone is visible
The waterfall showed only the model's 400-1200 Hz window, so a tone outside it
was absent from the picture entirely. Measured on keyed audio, the brightest
column in that narrow view swings 1.01x between key-down and key-up against
13.76x for a tone in range - it carries no keying at all, so the operator could
not tell a signal was present, let alone where it was. Markers alone could not
fix that: they pointed at a frequency with nothing drawn there.

compute() now takes an optional bin range, defaulting to the model's own, so the
decoder path is byte-identical and the golden-vector test still holds. The
display asks for DC to Nyquist, 129 bins against 65. The FFT already computed
every bin - this only changes which are kept - so the cost is a wider copy.

The decoder window is framed and faintly lifted, since half the picture is now
outside what the model reads and nothing said which half.

Marker fixes found while reviewing the render: the tone marker was orange, which
is a colour the inferno ramp itself passes through, so a marker sitting on the
trace it pointed at was indistinguishable from the keying gaps in that trace -
invisible in exactly the case it existed for. It is cyan now, and both markers
are pips in a gutter above the spectrum rather than lines across it.

Also from the release audit:

- compute()'s bin-count guard was written as a three-term disjunction, which any
  custom range satisfies regardless of bin count, leaving the model invariant
  unenforced for the caller most able to break it. Rewritten as an implication,
  with a Nyquist bound so no range can index past the FFT output.
- signalStrength was gated on a confirmed out-of-window tone, which is false when
  detection fails - and it fails for a slow fist, measured at prominence 2.5
  against a 4.5 threshold for 15% duty. So the meter still read half scale beside
  an empty transcript. It now requires a tone confirmed decodable: 11 flow
  combinations, 3 wrong before, 0 wrong after.
- detectedToneHz never expired, so after retuning into the band the hint kept
  naming the frequency the operator had left, indefinitely. It now clears after
  10 s without a tone, which is clear of any real gap - the longest being 1.7 s
  between words at 5 WPM.
- The waterfall label read estimatedPitch while the hint read detectedToneHz, two
  numbers up to 800 Hz apart both claiming to be the tone. Both read the latter.
- Removed a redundant toFloat() that the compiler warned about.

Accessibility, untouched until now: the waterfall was a bare Canvas and the AMSAT
day cells bare Boxes, so both announced nothing at all - on the status page that
is the entire content of the screen. Both now carry a contentDescription naming
the tone or the day's worst status and report count. The AMSAT tap target goes
from 28 dp to 48 dp with the coloured tile still 28 dp, so the grid keeps its
density. Strings in all nine locales for both modules.
2026-08-23 05:50:59 +00:00
mckero 984a139a81 feat(amsat): let the operator choose the day-cell style
Opinion split on the stripes, so Settings > Other now has a switch. On by
default, since the flat tile it replaced hid intra-day outages, which is the
problem the stripes were introduced to solve.

Flat mode is deliberately not the old behaviour. The old cell took its colour
from the first slot with a report and its count from that same slot, so a day
that worked in the morning and failed all afternoon read as "worked" - measured
across eight representative day shapes, two of them had their failure hidden
outright, and the count reported 1 where the day held 24 reports. Flat mode now
takes the day's worst status and the day's total count, so the summary can
understate detail but not hide bad news. The help text says so, in case someone
turns the switch off expecting the tile they remember.

The count is drawn in black or white by relative luminance rather than always
white: on the telemetry amber, white measured 1.83:1 against WCAG's 3:1 for
large text, and that cell does carry a count whenever a day held nothing but
telemetry reports. All six status colours now clear 3:1, the worst being 3.03.

SatStatusViewModel collects the setting rather than reading it once - the switch
is on another screen, so the operator is always elsewhere when they change it
and would otherwise return to the old style.

Strings in all nine locales.
2026-08-23 02:56:05 +00:00
mckero 018a3afd2b fix(amsat): mark satellites whose reports were crowded out of the global pull
The API caps at 500 records regardless of the hours requested. With 88 catalog
satellites, eight of them more active than 50 reports per 72 hours, quieter
satellites get crowded out. Measured live: the global pull returned 500 reports
covering 36 satellites, while the summary endpoint reported 743 reports across 38
satellites. 26 of 38 satellites had incomplete data, and two (PO-101_[FM] and
TEVEL2-6_[FM]) had zero reports in the global pull despite having reports in the
summary.

The summary endpoint (api/v1/summary.php) returns per-satellite report counts in
one request, so the fix adds one extra call rather than the 88-request
alternative of per-satellite pulls. A satellite whose global pull is incomplete
gets a subdued "68 / 116" marker next to its name, telling the operator the page
knows there is more data it could not fetch. The marker is silent when the
summary is unavailable or the counts match, so the feature degrades gracefully.

The earlier no-data grey (0xFFE8E8E8) already prevented the worst case: slots
crowded out of the global pull were marked as "we never looked" rather than
claiming "nobody reported". The marker now closes the remaining gap: the page
can honestly say "we know there are 116 reports for this satellite but we could
only show you 68 of them".

Also fixed a subagent mutation-testing residue: the coverage floor had been
moved from global (reports.minOfOrNull) to per-satellite (satReports.minOfOrNull)
and left in the tree. One test caught it (coverage is judged from all reports,
not one satellite's), proving the test has teeth.

Adds getAmSatSummary to IRemoteSource and RemoteSource, parseSummary to
AmSatRepository, and summaryCount to SatStatus. All eight test-file
implementations of IRemoteSource were updated for the new method.
2026-08-22 11:47:42 +00:00
mckero 3612e662e7 fix(amsat): distinguish slots we have no data for from slots nobody reported
Grey meant two different things. The API caps at 500 records however many hours
are requested: measured against the live endpoint, a 72-hour request returned 500
reports spanning only 49 hours, so the oldest 9.5 hours of the third day had no
data at all. Those cells were painted the same grey as "nobody reported", which
claimed knowledge we did not have - 352 of 3168 cells on a real page, a third of
the third day's column.

Slots entirely older than the earliest report in the response now use a lighter
grey. Coverage is judged from all reports rather than per satellite: a quiet
satellite has no reports of its own, but the slots it shares with the rest of the
response were still covered, so it must read as "not heard" rather than "unknown".

The two greys are now in the legend, which previously listed only the four active
states. That matters more than it sounds: on the live page 81% of cells are
"nobody reported" and 11% are outside our data, so a user looking at a mostly-grey
row had no way to tell a dead satellite from a gap in what we fetched. The legend
chips use a solid dot, so the two greys stay distinguishable despite the 25%
alpha background. Strings added to all nine locales.

Three tests cover it: a day entirely before the data starts, a day straddling the
boundary, and an empty response marking nothing as covered.
2026-08-22 10:27:44 +00:00
mckero 8f646d76f9 fix(amsat): align the status grid to UTC calendar days, one stripe per slot
Two defects in our own AMSAT page, both found by auditing the change that exposed
them.

The day columns claimed to be dates but were a rolling window anchored on the
fetch time. Fetching at 06:07 UTC put 17.9 hours of yesterday into the cell
labelled today; measured against a live amsat.org page of 1021 reports, 73% of
them landed in the wrong day column and none matched the official cell. Days are
now UTC calendar days and slots are fixed UTC bands - slot 0 is 22:00-24:00, slot
11 is 00:00-02:00 - so a cell's contents match its label whenever it is fetched.

The day cell painted one colour for the whole day, taken from the first slot that
had a report, so a satellite that worked all morning and failed all afternoon
looked identical to one that worked once - the reported symptom. It now draws one
stripe per two-hour slot in the same 64x28 dp footprint. Twelve stripes are about
5 dp each, roughly 15 px at 440 dpi, and runs of the same status merge visually,
so a day reads as a few blocks rather than twelve lines. Every density from ldpi
up allocates all twelve without dropping one, and the 4 dp corner radius leaves
95% of the end stripes visible. The report count text is gone; tapping a day
still lists every report from it, which was already the richer view.

buildStatuses and ApiReport are internal rather than private so the grid contract
can be tested. AmSatSlotBuildTest drives it directly: fetchStatus cannot be
tested here because the parsing around it uses Android's JSONObject, a JVM stub
that makes every call return null - eight of nine tests written against it failed
for that reason before being rewritten.

Also corrects three KDoc comments claiming 5 days when the code builds 3, and
records in AGENTS.md that the status colours are ARGB literals in core:data,
duplicated in MainTheme, which anything needing themeable or colour-blind-safe
colours has to fix first.
2026-08-22 06:59:39 +00:00
mckero 57d6f9d7ed chore(merge): drop dead leftovers from the upstream merge
Post-merge audit found code the merge left unreferenced:

- SettingsRepo: keySatelliteUrls / keyTransceiversUrls / separatorUrl were
  upstream's list-shaped data-source keys. The merge kept the fork's map-shaped
  DataSourcesSettings, so these three had a definition and zero uses.
- feature/status/res/drawable/ic_refresh.xml: SatStatusScreen imports
  core.presentation.R only, so its R.drawable.ic_refresh resolves to the
  core copy; the feature-local copy was never addressable. It was the only
  file under feature/status/src/main/res, so the directory goes with it.

Verified zero references with a repo-wide grep before removing each symbol.
compileReleaseKotlin plus core:domain / core:data / feature:map /
feature:roaming unit tests stay green.
2026-08-20 13:24:45 +00:00
mckero a654735337 merge: upstream rt-bishop main (18 commits) with conflict resolution
Merges rt-bishop/Look4Sat main (a42a5f1f, 18 commits: AMSAT status page,
customizable data sources, Doppler calculator, radar compass offset, per-sat
offset memory, localized date formats) into the fork's 30-commit audit
baseline.

Conflict resolution policy (user-directed):
- AMSAT feature (AmSatRepository, SatStatusScreen/ViewModel, SatStatus model):
  upstream version, which the user judged better built. MainScreen routes
  Screen.AmSat through SatStatusDestination().
- Localization: our values-zh/values-tr restored (upstream's merge dropped the
  fork-only strings); new upstream strings (sat_group counts, compass offset,
  frequency offset help) added in EN + ZH.
- fork-only features kept (CW decoder, Mutual/Roaming, WaveLog, APRS, Log tab,
  custom TLE/transceiver source switches): ours.
- Both sides' additions merged where independent: radar compass offset fields
  (Settings/SettingsRepo/RadarState), calculatorOffsetKHz action, wider linear
  transponder detection, deduplicateTransponders + its tests, sunrise/sunset
  tests merged with the moon hour-angle test.
- Sources kept as the fork's map structure (DatabaseRepo depends on it);
  satelliteModes list re-added for SelectionRepo. getSatelliteTypesIds /
  setSatelliteTypeIds re-added to ISettingsRepo+SettingsRepo; SharedDialog
  re-added to Components; providePairedBluetoothDevices added to
  IMainContainer/MainContainer.
- Icons renamed upstream (ic_satellites->ic_sputnik, ic_radar->ic_satellite)
  applied to Navigation/MutualScreen.

Build verified: all modules compileReleaseKotlin + unit tests
(core:domain, core:data, feature:map, feature:roaming) green.
2026-08-20 12:35:01 +00:00
mckero 7319cf8f5b fix(coroutines): propagate cancellation in remote source and status view model
RemoteSource's five suspend functions and SatStatusViewModel's two fetch paths
caught bare Exception, which also swallows CancellationException. When the
owning scope is cancelled (screen leaves, app closes) a cancelled network call
was reported as a null/error result instead of stopping: the caller kept
running until the next suspension point, and SatStatusViewModel wrote state
updates into an already-cancelled scope. Correct coroutine hygiene is to let
cancellation propagate - rethrow CancellationException before the generic
catch. Verified semantically with an asyncio probe: a swallowed cancel returns
a normal-looking null and the caller continues; a propagated cancel stops the
coroutine immediately.

No behaviour change for real errors; :core:data and :feature:status compile.
2026-08-17 16:46:09 +00:00
mckero 828f720955 fix(status): show gray cell instead of crashing on an empty day
AmSatParser deliberately uses getOrNull + mapNotNull while reading each day's
12 slots, so a shortened HTML row can legitimately produce SatDay(slots=[]).
StatusRow then selected the first non-gray slot and fell back to slots.first(),
which throws NoSuchElementException and crashes the entire AMSAT status screen.

Use firstOrNull for both lookups and render a zero-count gray placeholder when
no slot exists. Real amsat.org HTML currently has all 41 satellite rows at the
full 73 cells, but the parser's own tolerance contract means the UI must handle
what it can emit.

Verified against the live page: parser matches 41/41 rows and 477/477 reports;
:feature:status:compileReleaseKotlin BUILD SUCCESSFUL.
2026-08-14 15:27:43 +00:00
Arty Bishop 7cdc2952dc v4.4.6 - AMSAT status page, fully customizable data sources 2026-08-13 09:47:26 +02:00
Arty Bishop 24eebdef74 Consolidated app dialogs and tweaked bottom sheets 2026-08-11 14:14:14 +02:00
Arty Bishop 3f5b48f270 Integrated the AMSAT status page created by MCKero6423 2026-08-11 14:05:55 +02:00
mckero 10eb84690e Added AMSAT satellite status tracking page (#234) 2026-08-08 14:26:36 +02:00
mckero de85aa1e8b refactor(amsat): align with Material3 conventions per PR #233 review
Addresses feedback from rt-bishop/Look4Sat#233:

1. Migrate hardcoded colors to MainTheme colorScheme
   - Extend darkScheme: tertiary (Active 0xFF648FFF), tertiaryContainer (Telemetry 0xFFFFB000)
   - Extend lightScheme: tertiary (0xFF3C6FE0), tertiaryContainer (0xFFE09800) for contrast
   - Remove top-level Color() constants from SatStatusScreen.kt
   - Add statusColorOf() mapper using MaterialTheme.colorScheme

2. Move HTTP implementation from domain to data layer (Clean Architecture)
   - Delete AmSatApiClient.kt from core:domain (violates AGENTS.md: "Pure Kotlin, NO Android deps")
   - Migrate to IRemoteSource/RemoteSource in core:data (uses existing OkHttp3)
   - AmSatRepository now depends on IRemoteSource instead of AmSatApiClient

3. Inline JSON parsing (prepare for java.time migration)
   - Parse AMSAT API responses (names, reports) in AmSatRepository
   - Time parsing still uses manual logic (java.time desugaring in follow-up)

Before:
  - Hardcoded Color(0xFFXXXXXX) in UI + Repository (no theme support)
  - HttpURLConnection in domain layer (architecture violation)
  - AMSAT colors duplicated across modules

After:
  - MaterialTheme.colorScheme.tertiary/tertiaryContainer (light/dark adaptive)
  - HTTP via data layer RemoteSource (follows AGENTS.md architecture)
  - Single source of truth for AMSAT colors

Ref: https://github.com/rt-bishop/Look4Sat/pull/233#discussion_r1868599947
Ref: AGENTS.md "core:domain - Pure Kotlin (JVM). NO Android dependencies."
2026-08-05 15:26:42 +00:00
mckero 90bb584813 fix(status): drop appcompat tint attr from ic_refresh (library module has no such attr) 2026-08-05 08:53:05 +00:00
mckero f789a15338 fix(status): AMSAT page load failure, refresh spinner, retry button
- P0: fetchStatus() now runs on Dispatchers.IO - the previous
  synchronous URLConnection on the main thread threw
  NetworkOnMainThreadException and showed "load failed" on every open
- P1: refresh button rotates a vector icon (ic_refresh) instead of the
  "↻" text glyph, whose off-center font metrics made the spinner
  orbit around a shifted pivot
- P2: error state gains a Retry button (4 locales); amsat_refresh
  string added (5 locales)
- versionCode 456 (bump for reinstalling over 455), versionName stays 4.5.5
2026-08-05 08:50:01 +00:00
mckero 6221516893 feat(amsat): 4.5.5 official AMSAT API + edge-to-edge status page
- Replace HTML parsing with the official AMSAT Satellite Status API v1
  (catalog.php + reports.php, JSON): AmSatApiClient (pure JVM, hand
  rolled ISO-8601/epoch parsing for minSdk 24) + rewritten
  AmSatRepository (satellite list from catalog, reports slotted into
  6 days x 12 two-hour slots, status colors per report value)
- Fix edge-to-edge: status bar / navigation bar insets on the status
  page (refresh button and update time were unreachable)
- Version 4.5.5 (455), What's new in all 5 locales
2026-08-05 08:12:17 +00:00
mckero c9cab8457d chore(i18n): translate all code comments to English
All Chinese comments (//, /* */, KDoc) across core/app/feature/build-
logic translated to English (550 lines, 73 files after FT8 rollback).
Code logic untouched - comment text only. Verified: all modules
compileDebugKotlin BUILD SUCCESSFUL.
2026-08-05 07:59:35 +00:00
mckero 72f507163a fix(status): shrink day cells so long satellite names stay visible
User feedback: on narrow screens long names (e.g. AO-123_[FM]) were
truncated before the mode tag. Scaled the 6 day cells down
proportionally (weight 1f -> 0.8f, height 26dp -> 24dp) and widened
the name column (weight 1.4f -> 2f) in both header and rows.
Verified: :feature:status:compileDebugKotlin BUILD SUCCESSFUL.
2026-08-04 16:41:41 +00:00
mckero f7402a3531 feat(status): 4.5.3 AMSAT satellite status page + radar log pass divider
New feature/status module: fetches https://amsat.org/status/ and
renders a live status grid in the official site colors:
- Parser (AmSatParser): 47 satellites x 6 days x 12 two-hour slots,
  official colors (blue=Active, orange=TLM/Beacon, pink=Not Heard,
  deep-orange=Conflicting, gray=none); 598+ report details extracted
  from inline JS tooltips (callsign/date/time/grid)
- Three-level viz: color grid -> report count -> tap day cell opens
  report list dialog
- Manual refresh with spin animation + last-updated timestamp +
  legend row; loading/error states
- New "AMSAT" entry in the More menu (Screen.AmSat), integrated with
  page-order / hide-page settings (SettingsScreen screens list,
  defaultSubMenuOrder, allNavItems, migration for existing users)
- AmSatRepository via IRemoteSource.getStatusHtml() (UA header);
  shared remoteSource promoted to a lazy class property in MainContainer

Radar page Log tab: local entries now grouped by pass session with a
thick divider + satellite label between groups (matches the log page).

What's new updated in EN/ZH/TR/IN/ID. Version bumped to 4.5.3/453.
Not released (user gates all releases).
2026-08-04 15:57:41 +00:00