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.