Arblant
Railynx / UI_NOTES.md
UI_NOTES.md226 lines · 13.3 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
# PC/Android Flutter UI: v13 notes (2.0.0+7, 2026-10-28)

Settings build. An invisible-only performance build (half-res blob
frames + scan isolate + repaint boundaries) was tried first and the
user's on-device verdict was «اصلا خوب نشد» — reverted completely;
probe_engine.dart and vless_scanner.dart are byte-identical to +6
again. What shipped instead is his settings design:

- Controller: fxBlobs/fxWashes/fxDockBlur/fxButtonFlow/fxRingGlow/
  fxRowAnimations + the pre-existing animatedAurora are the seven
  switches; presetTable holds full/balanced/performance bundles;
  uiPreset is a derived getter (values -> preset name or 'custom').
  applyPreset/setFx persist all keys via _saveFx (fx_* + aurora_anim).
- SettingsPage (lib/ui/settings_page.dart): pushed from the header
  gear; live backdrop preview behind it; preset cards + a GlassCard of
  AppSwitch rows; Custom chip appears when uiPreset == 'custom'.
- Wiring: AuroraBackdrop(showBlobs/showWashes) gates the blob layer
  and the _WashPainter washes; _Dock skips its BackdropFilter when
  fxDockBlur is off; the hero button's live flow needs fxButtonFlow;
  ScanRing(glow:) drops the blurred underlay + sweep shader for a flat
  ringRampColor arc; result rows skip their entrance builder when
  fxRowAnimations is off.
- Tests: test/app/settings_test.dart — defaults, both presets,
  custom derivation, prefs round-trip across controller instances,
  and the page flow (gear -> preset tap -> switch flip -> Custom).

# PC/Android Flutter UI: v12 notes (2.0.0+6, 2026-10-28)

The user's nine on-device fixes. Engine probe SEMANTICS unchanged
(golden suite + live mock test guard them); the scanner only gained a
ScanAbort socket registry for instant Stop. Details per fix are in
CHANGES.md; the non-obvious ones:

- Ring (1): SweepGradient itself was verified positional-correct by a
  pixel probe (0°=teal … 350°=amber); the visible amber tip came from
  the round start cap sampling the ramp's last degrees. Fix: butt caps
  + a head dot filled with ringRampColor(fraction) — pure math, same
  on every renderer.
- Blobs (6): CSS blur(72px) of a solid disc ≈ plateau alpha .42 out to
  (R−σ), tail to R+2σ. The painter now draws radius R+64 with stops
  [0, (R−30)/(R+64), (R+25)/(R+64), 1] and alphas [.42, .40, .15, 0].
- Backdrop split (3): _WashPainter (static, own RepaintBoundary,
  shouldRepaint=false) under _BlobPainter (animated). Identical pixels,
  composited in the same order; only the blob layer ever repaints.
- Multi (5): grouping key = domain|port|path|uuid (exactly the probe
  inputs). Controller holds parsedAll + groupResults; `results` mirrors
  the active group; group chips in the results header switch it. The
  single-config path is untouched when parsedAll has ≤1 config:
  resolveBase(), field edits and prefs behave as before. Editing the
  domain field away from the parsed first config exits multi mode.
  Test-measurements key on 'configIndex|ip' in multi (same IP can be
  clean on two servers with different delays), plain ip in single.
- Sheet (8): the whole sheet body scrolls as one; Test/Generate pinned
  in a bottom bar. In multi the stepper is replaced by an 'N pasted'
  label and each row shows its config's name.

# PC/Android Flutter UI: v11 notes (2.0.0+5, 2026-10-28)

- **Release-blocker fixed: INTERNET permission.** The v9 integration set
  the label/namespace/icons on the flutter create manifest but never added
  `<uses-permission android:name="android.permission.INTERNET"/>`, and the
  template only carries it in debug/profile manifests. Result: v9 and v10
  release builds could not open one socket — every probe failed instantly,
  the ring filled 254/254 with no results, and post-scan actions no-oped.
  Verified by `aapt dump permissions` on the shipped +4 APK (INTERNET
  absent), fixed in the main manifest, guarded by
  test/app/manifest_test.dart. The Kotlin app's manifest had INTERNET +
  ACCESS_NETWORK_STATE; the Dart engine needs only INTERNET.
- Subtitle "Shafaq edition" → "Twilight edition" (user: use the English
  equivalent of شفق).
- Keyboard fix (user report): the v10 config fields set
  autocorrect/enableSuggestions to false, which makes Samsung's keyboard
  hide its clipboard/suggestion toolbar (password-field behavior). Both
  flags removed everywhere; v9 did not set them.
- The +4 arm64 asset on GitHub was downloaded back and sha256-matched
  against the CI artifact (075b633d…) — the release bytes were never
  corrupt; the user's local v8a install error remains unexplained by the
  file (his A15 is a confirmed 64-bit OS). The +5 arm64 asset gets the
  same download-back hash verification after shipping.

# PC/Android Flutter UI: v10 notes (HTML-exact pass, 2026-10-28)

## Status

- Compiled and tested by Muse this round: `flutter analyze` clean of
  errors, `flutter test` 116/116 green, and the phone layout rendered to
  PNGs and eyeballed against the HTML mockup in a browser (idle, results,
  sheet). The on-device verdict is still the user's.
- What changed vs v9: only presentation + user-visible strings.
  lib/engine/*, the golden corpus, prefs_store and probe_engine are
  byte-identical (verified against repo main). Controller edits are two
  toast strings (copyTop5, generateAndCopy); workbench_model edits are
  the Custom explainer text and the shortage rule/text (HTML wording,
  applies to every mode except best, as in the mockup).
- Performance rules used (no visual loss): no BackdropFilter under cards
  (the backdrop is a soft gradient field; blurring it is a no-op
  visually), aurora = one CustomPainter in a RepaintBoundary repainted at
  ~30fps, the ring repaints only on progress, row entrance animations run
  once per new IP. The dock keeps the app's only backdrop blur.
- Fonts: Manrope + JetBrains Mono statics in fonts/ (instanced from the
  Google Fonts variable files, OFL), registered in pubspec.yaml.
- Header mark on both platforms' home pages is assets/cat.png (the yellow
  cat) per the user's 2026-10-28 correction; launcher icons unchanged
  (Android = cat already, PC exe = aurora).

# PC/Android Flutter UI: v9 notes

## Status (read first)

- **Not compiled and not tested.** The build environment has no Flutter SDK. The first real check is
  `flutter pub get`, `flutter analyze`, `flutter test`, then `flutter build windows` and `flutter build apk`.
  Please send the first failure output back, and I will fix it.
- **Controller: unchanged.** Auto-parse already existed: `onLinkChanged` (450 ms debounce) and
  `pasteFromClipboard` (immediate). Only the widgets changed.
- **Storage layer: unchanged, and it is a blocker for the Android update path (see below).**

## Blockers and risks for the Android build

1. **Settings will not carry over from the existing Kotlin app.** The Kotlin app stores settings in
   Android SharedPreferences named `railway_scanner_prefs`. The Flutter port stores
   `railway_scanner_prefs.json` next to `Platform.resolvedExecutable`. On Android that folder is
   not a writable place for app data, and it is not the SharedPreferences store. So an APK installed
   over the Kotlin app would start with empty settings, and writes are silently dropped (`merge`
   catches the error). Fixing this needs either a native bridge to SharedPreferences (a
   MethodChannel, no new package) or `path_provider`. I did not change the storage layer, because
   the brief said to keep it as it is. This needs a decision.
2. **Application id.** Run `flutter create --platforms=android` and then set `applicationId` and
   `namespace` in `android/app/build.gradle.kts` to `com.arblant.railwayipscanner`, the id of the
   existing Kotlin app. Use the same signing key and a `versionCode` above the Kotlin app's, or Android
   will refuse the install as an update. `--org com.arblant` on its own gives a different id.
3. **Animated blur cost.** Each glass card is a `BackdropFilter`. While the aurora animates, the
   backdrop under every card changes each frame, so each card re-blurs each frame. There are about
   four cards on screen. This is the likeliest cause of dropped frames on a mid-range phone. It has
   not been measured. If it drops frames: turn the aurora off by default on Android, or lower the
   blur sigma, or move the aurora into a separate `RepaintBoundary` layer that is not under the cards.

## Three user decisions (applied)

1. Header mark = the aurora-circle artwork, `assets/icon.png`, declared in `pubspec.yaml`. The HTML
   icon is not used. Name "Railynx", subtitle "Shafaq edition".
2. No Parse button. Parsing is automatic: 450 ms after the field changes, or immediately on Paste.
3. Paste button kept.

## Repaint strategy (performance)

- Aurora: one `AnimatedBuilder` repaints the blobs only while the aurora is on. It is a `Stack` of
  radial gradients, not a `CustomPainter` per card.
- Ring: a `TweenAnimationBuilder` rebuilds only the ring's `CustomPaint`, and only when the
  progress fraction changes. `shouldRepaint` compares the fraction.
- Glass: a `BackdropFilter` per card (blur 18). See blocker 3.
- Results rows and the rest of the page: no animation. The list only rebuilds when controller
  notifies.

## Element checklist (HTML to Flutter)

| HTML element | Flutter counterpart | Status |
|---|---|---|
| `.aurora` orbs (teal, indigo, pink, amber, drifting) | `AuroraBackdrop` | Done. Amber added. Drift kept. |
| `.veil` and noise | none | Not reproduced (the noise was a texture; left out on purpose). |
| `.glass` cards | `GlassCard` with `BackdropFilter` | Done. |
| header brand mark | `Image.asset('assets/icon.png')` | Done (user decision 1). |
| header title and version | `Railynx v…` | Done. |
| subtitle "Shafaq edition" | `Text` | Done. |
| status pill (`#statusLine`) | `Pill` with `statusLine` | Existing, kept. |
| close button | `IconTap` | Existing, kept. |
| config card, collapsible | `_ConfigCard` | Existing, kept. |
| Paste button | `ChipButton('Paste')` | Kept (user decision 3). |
| Parse button | removed | Done (user decision 2). |
| summary chips (UUID / Subnet / Path) | editable `TextField`s under the link | **Differs.** The HTML's chips are read-only summaries. The Flutter port keeps editable fields (the app needs them). |
| animated aurora switch | `AppSwitch` | Existing, kept. |
| scan ring (176 px) | `ScanRing`, sweep arc, glow, animated | Done. |
| ring centre count + "of 254" | `_ScanHero` | Done. |
| ring status line (Ready / Scanning / Done / Stopped) | `_ScanHero` status text | Done. |
| "last checked <ip>" line | `_ScanHero` | Done. Wording is "last checked", not "probing": the engine only reports completed probes. |
| linear progress bar under the ring | removed | **Differs.** The HTML has no bar; the ring is the progress. |
| start / stop button (gradient) | `GradientButton` | Existing, kept. |
| fastest card (gradient border, big latency, dimmed dots) | `_BestCard` | Existing, kept. |
| results rows (rank, dimmed-dot IP, bar, colour chip) | `_ResultRow` | Existing, kept. |
| copy feedback states | `_ResultRow` feedback | Existing, kept. |
| bottom dock "Copy top 5" / "Build configs" | `_Dock` | Existing, kept. Width capped at 560. |
| footer "Made by arblant & Muse" | `_Footer` | Existing, kept. |
| workbench sheet (count, modes, rows, test, copy) | `workbench_sheet.dart` | Existing, behaviour unchanged. Not restyled in this round. **Differs from the HTML's look.** |

## Desktop layout

The mockup is a phone design. On Windows it is not stretched: one column, centred, capped at 560 px,
with the aurora filling the rest of the window.

## Still to verify on a device

- Ring animation feels smooth on a mid-range phone (blocker 3).
- Glass blur looks right on Windows (blur is GPU-dependent).
- The workbench sheet has the HTML's look: it is not restyled in v9.

## TEST BUILD 2.0.0+8 (not released) — lag diagnosis

The user's verdict on +7: appearance changes fully with the switches,
but the lag is constant and identical with everything off and on — so
the bottleneck is not the visual effects. Audit found ONE unconditional
perpetual animation (OrbitArt's spin, empty state) and fixed it to
follow the aurora-animation switch. The rest is measured, not guessed:
Settings -> Diagnostics -> Performance overlay puts Flutter's frame
graphs on the user's phone; his readings decide the next fix.

## TEST BUILD 2.0.0+9 (not released) — raster diet

Measured diagnosis (his overlay screenshots): UI thread healthy
everywhere; raster thread 25-28 ms avg in idle, scroll and scan — the
lag is GPU drawing cost, constant because the pricey primitives are
always on screen: full-screen gradient blobs re-rasterising per tick,
a MaskFilter blur for the ring glow, gaussian shadows up to blur 44/80,
and a permanent Opacity+Transform layer on every settled result row.
+9 removes those costs one by one (see CHANGES.md) without touching
the engine or the layout. Judged by re-running the same 3 screenshots.

## 2.0.0+10 — original look default, optimisations as switches

Per the user's order: default appearance = the ORIGINAL (pre-first-
optimisation) look with all features intact; the whole raster-diet
became Settings switches with plain-language cost notes (Rich shadows,
Blurred glow, Half-resolution aurora, Lightweight rows) under
Original/Balanced/Performance presets, plus a Frame rate selector
(Max/60/30, Android preferredRefreshRate via the railynx/fps channel,
MainActivity.kt). Paint-level flags are mirrored into app/look.dart
(Look) by the controller for widgets without a controller handle.
The +8 performance overlay is removed. Released to GitHub v1.