Skip to content

Refactor/home page background - #522

Open
whes1015 wants to merge 64 commits into
mainfrom
refactor/home-page-background
Open

Refactor/home page background#522
whes1015 wants to merge 64 commits into
mainfrom
refactor/home-page-background

Conversation

@whes1015

@whes1015 whes1015 commented Aug 9, 2026

Copy link
Copy Markdown
Member

No description provided.

@whes1015
whes1015 force-pushed the refactor/home-page-background branch from 551706a to 0481412 Compare August 9, 2026 15:25
@whes1015
whes1015 force-pushed the refactor/home-page-background branch from 0481412 to 25b7bd4 Compare August 9, 2026 15:32
@whes1015
whes1015 force-pushed the refactor/home-page-background branch from 92f3d06 to 42589dc Compare August 10, 2026 14:09
Wire the home sheet's 近 1 小時降雨趨勢 card to
GET /api/weather/rainforecast/{code} (exptech.dingbot.tw), decoding the
start/60×mm envelope into RainHourTrend with loading/error states.
Keep the blur/dim layer's tree shape constant (ImageFiltered.enabled +
an always-present transparent scrim) so the sky's element is never
re-parented when the sheet starts climbing or content scrolls — which
used to reset WeatherSkyBackground's shader cache and flash the flat
fallback colour.
CWB's ones digit refines the glyph: showers, fog banks, hail and
lightning each get their own icon (with 大雷雨 filled, the heaviest
rung), while the accent colour keeps following the resolved mode so the
family tint stays consistent.
Featured earthquake card on primaryContainer plus a two-column ranking
grid with accent-coloured icons and badges.
RainHourTrend gains a structured summary (grade, sustained, stop time)
that drives a one-line reading under the card title; the chart's axis
now labels minutes-from-now ticks instead of Taipei wall clock times.
Pre-render one PNG per speed bucket with a black outline baked in
(MapLibre's icon-halo only works on true SDF images), pick the image via
a step expression, and expose readingIcon so sheets can reuse the arrow.
The speed curve is drawn in per-bucket runs (the same white→pink ramp as
the map arrows) instead of one flat line, and the X-axis ticks use
speed-coloured WindArrowIcon glyphs; the station hero gains the same
rotating arrow icon.
fl_chart requires one tooltip item per touched spot; a touch at a segment
boundary hits two bars at the same x, and deduping them shrank the list
— drawTouchTooltip threw "tooltipItems and touchedSpots size should be
same". windTooltipItems returns every item; the identical duplicates
overlap and read as one card.
Bump the native memory cache to 24M and warm outward from the selected
frame (0 ±4, then ±12 and beyond while space remains): warm() gains a
fillUntil target so injection stops at the mirror's cap instead of
churning LRU, and the timeline re-centres its warm band when scrubbing
past it.
Extract the radar layer's scan-range + county + township overlay into a
ScanRangeOverlayChrome mixin and reuse it for the QPESUMS layer, so the
next-1-hour forecast layer draws the same boundaries as the radar.
After a hot restart the engine can still own the previous isolate's view
id, so UiKitView creation fails with recreating_view and onMapCreated
never fires. Remount the map with a fresh key when it isn't ready within
4s (bounded retries), and treat the benign platform exception as neither
a log entry nor a fatal crash.
Replace the native MapLibre compass (rendered inside the platform view,
so any Flutter overlay — typhoon forecast tooltips, an expanded sheet —
paints over it) with a Flutter MapCompass at the top of the map stack,
driven by the camera bearing and tapping to reset north.
未來1小時 → 未來 1 小時 for the four Chinese variants.
Legacy handled tap → navigation inside notify.dart; the rewrite had it
as a private method on the app service host. Move it to
notification_routes.dart (which already owns the channel → route table)
as routeNotificationTap — the app host just registers it on
NotificationTaps — with an injectable navigate for tests.
@whes1015
whes1015 force-pushed the refactor/home-page-background branch from 8306f93 to 7e3c228 Compare August 10, 2026 16:00
An empty series in the rainforecast envelope (or an all-zero one) is the
API's way of saying no rain this hour, so decode returns a dry trend
instead of failing, the home trend card hides itself, and the chart
paints minutes outside the window as one continuous grey band rather
than full-height bars. A no-data pane labels the missing span.
When the next-hour forecast is dry there is no rain chart to show, so the
hero slot swaps the trend card for the 24-hour forecast card, which starts
as a one-glance summary and expands into the full card as the sheet is
pulled up — one widget, not two cards.
所在地 is a place, not a region: frame the camera on the live GPS fix
(a small span around it) instead of the whole township, while saved
townships keep their bounds-centred framing. Falls back to the township
box, or the whole island, when no fix is available.
math.pow(x, 2) routes through the generic power path and needs an `as
double` cast; x*x is a plain multiply on the already-double operands.
Equivalent up to floating-point rounding.
nearestSample ran a linear scan for every axis tick and tooltip — a 7 d
wind series is ~170 points, and the x-values are monotonic, so a binary
search finds the same index in O(log n). Ties resolve to the earlier
index, matching the old strict-< scan.
The 8 offset neighbours that bake the black outline were declared twice —
once in the map PNG renderer, once in WindArrowIcon — with identical
values. Hoist one windOutlineDirs constant both use.
The frame ids were sorted as strings, then immediately re-sorted as ints,
wasting a pass and repeatedly int-parsing inside the comparator. Keep the
int sort only.
Clamp the chunk end with min() and always pass the same sublist form
instead of branching on whether the last chunk is short.
The draggable sheet re-rendered its whole backdrop every scroll tick:
the sky shader repainted each frame even under the blur, the scroll blur
re-computed every tick with nothing cached behind it, and card rain kept
running on cards already scrolling off-screen.

- Freeze the sky (active=false) the instant the list scrolls — the blur
  about to cover it hides the hold, and holding the last frame lets the
  blur layer cache; the state clock stops with the ticker, so resuming
  at the top restarts where it left off.
- Quantise the scroll blur/dim to a 4-step ladder, so the full-screen
  blur re-renders only on level changes instead of every tick.
- Rebuild HomeContent's ListView shell only when the sky re-bakes; the
  scroll-driven reveal/expansion lives on one inner builder, merging the
  hero's bottom-gap and dry-hour forecast builders.
- Stop RainOnGlass and the card-water LiquidFun worlds the moment the
  position gate closes (cut the water outright instead of draining), and
  resume on the next rebuild once the gate reopens.
The sky screen pass was a full-screen shader whose output is a pure
vertical gradient of a fixed LUT column, so it is now CPU-baked once per
re-bake (4×1024) and stretched per frame. The cloud shader's LUT probes
share a constant u, so the horizontal filtering moved to a CPU-extracted
1×141 column at bake time and each probe is a vertical 2-tap. The galaxy
layer and starmap, never loaded in production, are gone. Base/haze sky
colours arrive as baked uniforms, and dead uniforms (iFrustumA/C,
iSunElevParam) are dropped. A parity test pins the baked gradient to the
reference shader within its own dither.

The rain particle field no longer allocates a Color or boxes a clamp num
per drop each frame (up to 1792 drops at storm grade); the packed ARGB32
and branch clamps are bit-identical.

Forcing an experimental weather mode now drops the live rain/snow/humidity
overrides, so a forced clear sky no longer keeps raining. The resolution
is a pure function with a regression test.
The picker's header gains a reorder button that opens a fixed-height
editor over the sheet (two stacked drag sheets felt broken). The saved
order is a list of layer ids resolved against each surface's layers via
orderedLayers(), so a layer added after the order was saved appends at
the bottom, a stale id is ignored, and duplicates count once — future
layers can never throw. Every drop persists; a reset button restores the
declared order.
The bottom-nav More tab's default-layer picker was missing the QPESUMS
layer. Add it alongside radar, with its own nav label and icon, so users
can default the Map tab to the 1-hour precipitation forecast.
Replace the broad three-way split (reference / weather / hazard) with the
six categories the app's data actually falls into — earthquake, typhoon,
weather observations, satellite, radar, and daily life. The layer picker
and the default-layer settings page both group under these headers, and
`categoryOf` falls back deterministically so a layer added later still
resolves without crashing.
The picker's order sheet previously only reordered layers within a
category. Persist the category order alongside the layer order (same
tolerant merge, same reset) and make a category header draggable — it
moves its whole block, while a layer drag still clamps to its category
and a drop always snaps to a group boundary so a block can't land inside
another group.
Reordering a flat list of categories and layers mixed together got
confusing once categories were sortable — a header drag moved a whole
block but a stray drop could still land mid-list. Make the editor
two-level instead: level 1 reorders the categories (draggable, persisted
to the category order), and tapping a category drills into level 2 to
reorder just its layers. Each level is a homogeneous list, so no block
banding or boundary snapping is needed, and a single-layer category isn't
drill-in-able at all.
Set the default category order to earthquake → radar → typhoon →
weather → satellite → life, moving the radar group up past typhoon. This
is the declared order `orderedCategories` falls back to when the user
hasn't saved a custom category order.
Advance the pinned maplibre-gl ref from d3deabf to 7c2e249 for the newer
native tile-bridge changes.
Drop highp float to mediump in the weather sky, card-water, rain-on-glass,
and sun-flare fragments — mediump is sufficient for their colour work and
is cheaper on tile-based GPUs.
Radar, satellite, and QPESUMS each had a near-verbatim API class and
repository impl differing only in the URL path segment. Collapse them
into a parameterised `FrameTileApi` (path + shared delta-frame decode)
and a single `FrameTileRepositoryImpl` that implements all three empty
domain interfaces; the provider layer still injects each overlay under
its own interface.
Centralise the `* pi / 180` conversion (11 sites) into
`core/geo/geo_math.dart`, share the value-ramp interpolation
`rampColor` between the rain and weather-station layers, and let
`LatLng` reuse degToRad instead of a private alias.
Sheet layers (weather stations, rain, lightning, RTS, typhoon, DPM)
each hand-wrote the same ten-plus no-op bodies for the MapLayer
members they don't use. A `MapLayerDefaults` mixin provides them once;
layers override only what they actually do. Also folds the shared
4/maxZoom floor into the defaults.
- Extract the code→station map and delta-seconds history decodes shared
  by the meteor weather/rain/lightning repositories.
- Move boundsFromPoints into camera_fit and reuse it from the report
  detail page instead of a second min/max loop.
- Use AppTime.utc for the rain-trend dry/placeholder "now" instead of
  DateTime.now, per the calibrated-time convention.
Generalize WeatherStationLayer over the snapshot/trend/observation types so
the rain overlay (which only differs in its interval-scrubbing source) is a
thin subclass instead of a parallel copy.
The weather / rain / lightning datasets share the station, latest, list, at,
and trend surface; fold the three near-identical clients and impls into one
parameterized MeteorSnapshotApi / MeteorSnapshotRepositoryImpl.
Three models each decoded {updated, cyclones} payloads and blank/coord
helpers by hand; pull the common decodes into typhoon_decode and lat_lng
pair helpers.
Replace the ad-hoc +8h DateTime arithmetic with one calibrated facade so
every wall-clock conversion reads the same way.
Add LegendDot, InlineLoading, trimTrailingZero, and an EventType icon key +
resolver so the dozen hand-rolled spinners, status dots, axis labels, and
event icons render from one place.
Centralize extent listening, the at-top flag, the grip, the frosted surface,
and the full draggable-sheet chrome (ExtentSheetChrome) that four sheets used
to copy verbatim.
…ndpoint

Put the wire paths shared by the ETag cache and the API clients in one
ApiPaths registry so they can't drift, reuse MeteorDecode in the frame list,
and delete the unused /api/v1/eq/report client.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant