Date/time buttons carry a two-line label ("From\nAug 19, 2026") but
sat in a fixed dp(40) row, so the date line was cut off and only
From/To was readable. My own compaction pass tightened that row from
48dp to 40dp and made it worse. They now measure wrap-content, at
11sp, with all-caps off (Buttons upper-case by default, which is
what pushed the date past the edge) and 4dp side padding instead of
the stock ~16dp.
Historical, normalized and removed points now render their
coordinates as the same tappable geo: link the live device popup
already used - MapActivity's WebViewClient hands geo: to the maps
app, so all four popups behave the same. Factored into gpsLink().
Drops the "View: Normalized" line from the device popup; the filter
panel already says which view is active.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The split carried over the wrong versions of two bits of chrome.
The filter toggle had become a full-width grey TextView bar, 48dp of
"Map Filters ▼" sitting above the map. Restored to what it was: a
small "☰ Filters" button on a wrap-content row.
The Leaflet layers control was being created with collapsed:false,
which pins the whole Streets/Satellite list open as a box over the
map. collapsed:true is what renders it as the floating corner button
that opens on tap. Its 13px font rule came back with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel is taller than a phone screen, and it was a LinearLayout
child with WRAP_CONTENT and no weight - so the excess was simply
clipped at the bottom edge. Everything from the accuracy multiplier
down was unreachable, which is where Apply, Reset, Export Raw and
Export Normalized had gone. Nothing was missing from the build; it
was drawn off-screen.
Wrap it in a ScrollView carrying the layout weight, so it shares the
screen with the map and scrolls to the rest. setFiltersExpanded now
hides the scroller rather than the panel inside it, or the collapsed
state would leave an empty scroller holding half the screen.
Then cut roughly 145dp of height out of the panel so most of it fits
without scrolling at all: the threshold readout goes from five lines
to two, the normalization summary from six to two (the level and
percentage are already on the line above it), button rows 48dp ->
40dp, the slider 42dp -> 28dp, and the section labels and paddings
lose a few dp each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pin sat dead ahead of the heading arrow no matter which way the
user turned, so the arrow looked like it was aiming at the item and
the item looked like it was always straight down the path of travel.
The cause is geometric, not a UI link. addSample() only accepts a
sample after 5 m of movement, so every sample lies on the line the
user walked. gradientBearing() correlates RSSI against centred
position, and on a collinear track each centred position is t*u for
a single unit vector u along the walk - so the sum is exactly
parallel to u regardless of the signal. The "estimated" bearing was
the direction of travel, echoed back.
That bearing then placed the pin in three separate paths:
preliminaryEstimate() projects along it, solve() overrides a
converged trilateration with it, and consensusEstimate() averages
the result. All three produced a pin straight ahead.
Refuse the bearing until the track has real width across its own
axis - the smaller eigenvalue of the position covariance, against
max(4 m, meanAccuracy/2). A straight walk with GPS jitter measures
about 1 m of spread and is rejected; an L of two 20 m legs measures
about 5 m and is accepted. Distances still place the item off-axis
on a straight walk, but which side is a real mirror ambiguity, so
the UI now says so and asks for a leg at 90 degrees instead of
inventing a side.
Also drops showFallbackPin(), which projected the pin along the raw
compass heading whenever no estimate was solved - a fourth route to
the same wrong place. With no estimate the pin now shows the item's
last known GPS fix, or nothing at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MapActivity had grown to 4,800 lines and was editing itself into
corruption - the field block had picked up seventeen duplicate
declarations of showNormalizationChanges, and that name was being
used for both the CheckBox widget and the boolean state it toggles,
which is what the compiler was actually failing on.
Fix the collision the way the class already handles the same pattern
elsewhere (showNormalized/normalizedMode): the widget is showChanges,
the state stays showNormalizationChanges.
Then move the bulk out of the activity:
HistoryNormalizer the GPS cleanup engine, its caches, thresholds
and the LocationPoint reflection helpers
MapExporter CSV export, including the 695-line writer and
the file-picker round trip
MapHtmlBuilder the Leaflet page, markers, trails and popups
MapFormat date/reason/colour/HTML/JS/CSV escaping shared
by all three
The activity keeps the UI - onCreate, the filter panel, pickers and
sensors - and feeds the builders through a Filters snapshot, an
Options struct and a two-method DataSource interface, so none of the
extracted classes reach back into its widgets.
Behaviour is unchanged. Also drops pendingExportMode, which was
written in two places and never read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>