First adaptation pass of the shared Skia console on Android: the obvious bugs, then the phone-sized UI. Four commits, in dependency order — each builds on the previous, so they ship as one PR.
fix(android): the console's pad probes survive the Controllers/Licenses pages (a64a22cc)
The MainActivity pad probes were one last-writer-wins slot. The Skia shell installs its probes once; a Compose screen the console opened over itself (Controllers, Licenses) overwrote that slot and nulled it on the way out — the shell never re-installed, so every gamepad press after closing the page was silently dropped until the process died. The slot is now a stack: push on install, remove by identity on dispose, dispatch consults the top. Every ordering Compose produces (cross-fades composing both screens at once, non-LIFO disposal) now self-heals.
fix(android): a console that cannot draw yields to the touch UI (45453103)
Connecting a controller could swap in the console over a SurfaceView nothing would ever paint — native create failed, render thread died, or a GL context Android reclaimed — leaving a gray screen for the rest of the process (Kotlin only logged the Dead event). SkiaConsole.healthy is now observable state folded into the gamepad-UI gate (touch UI takes over), and the native render loop ends itself through the Quit release order after 3 consecutive GL setup failures instead of hot-spinning gray.
feat(console-ui): Connected controllers is the console's own screen (9cefa0a3)
The Kotlin ConsoleControllersScreen takeover is replaced by a native screen in pf-console-ui — the console keeps its own input on the page (which is what made the probe eviction so common). Only the five platform-only actions go back to the host as one ConsoleCmd::PadAction { action, pad_key }: rumble, SC2 Bluetooth/USB grants, DualSense USB grant, DualSense haptics self-test. PlatformScreen::Controllers is deleted; the D7 suspension mechanism survives for Licenses. The row stays Android-only; desktop ignores PadAction. Deliberately deferred (recorded as a ponytail: comment): the live input test stays on the touch screen — the console's aggregated MenuSample is nowhere near a per-device axis/trigger readout; porting it means widening the pad-sample bridge.
feat(console-ui): the console takes the whole phone panel (d59a1a96)
System bars hidden while the console fronts the app (transient by swipe, restored on dispose) — also the safe-area fix: hidden bars report zero insets, so scrolled content no longer slices off at the visible gesture-bar line.
Phone density floor of the design-unit scale raised 0.6 → 0.75 (phones-only: the couch term wins on tablets/TVs); still the documented on-glass knob.
The library's sort/view bar appears only while it holds the pad (▲ / "Sort & view" in the legend), Apple-client style; the field takes the band back as it fades.
The store/platform subtitle under the focused title is gone (the cover badge already says it); detail band 84 → 64 units.
Grid guard: the two-column minimum shrinks covers to fit rather than clipping when a narrow viewport × high density leaves less width than two full cells.
Verification
cargo ndk clippy -p punktfunk-client-android -- -D warnings (arm64): clean at each commit.
./gradlew :app:testDebugUnitTest :kit:testDebugUnitTest -PskipRustBuild: green at each commit (incl. Roborazzi scenes; the console-controllers scene removed with its screen).
Honest caveat: pf-console-ui is cfg-gated to linux/windows/android, so its new unit tests (controllers screen, library) are compile-checked on macOS via the NDK clippy and first execute in Linux CI — watch the ci workflow on this PR.
On-glass: debug build installed to a Nothing Phone for the phone-UI pass.
First adaptation pass of the shared Skia console on Android: the obvious bugs, then the phone-sized UI. Four commits, in dependency order — each builds on the previous, so they ship as one PR.
## fix(android): the console's pad probes survive the Controllers/Licenses pages (`a64a22cc`)
The MainActivity pad probes were one last-writer-wins slot. The Skia shell installs its probes once; a Compose screen the console opened over itself (Controllers, Licenses) overwrote that slot and nulled it on the way out — the shell never re-installed, so every gamepad press after closing the page was silently dropped until the process died. The slot is now a **stack**: push on install, remove **by identity** on dispose, dispatch consults the top. Every ordering Compose produces (cross-fades composing both screens at once, non-LIFO disposal) now self-heals.
## fix(android): a console that cannot draw yields to the touch UI (`45453103`)
Connecting a controller could swap in the console over a SurfaceView nothing would ever paint — native create failed, render thread died, or a GL context Android reclaimed — leaving a gray screen for the rest of the process (Kotlin only logged the `Dead` event). `SkiaConsole.healthy` is now observable state folded into the gamepad-UI gate (touch UI takes over), and the native render loop ends itself through the Quit release order after 3 consecutive GL setup failures instead of hot-spinning gray.
## feat(console-ui): Connected controllers is the console's own screen (`9cefa0a3`)
The Kotlin `ConsoleControllersScreen` takeover is replaced by a native screen in `pf-console-ui` — the console keeps its own input on the page (which is what made the probe eviction so common). Only the five platform-only actions go back to the host as one `ConsoleCmd::PadAction { action, pad_key }`: rumble, SC2 Bluetooth/USB grants, DualSense USB grant, DualSense haptics self-test. `PlatformScreen::Controllers` is deleted; the D7 suspension mechanism survives for Licenses. The row stays Android-only; desktop ignores `PadAction`. Deliberately deferred (recorded as a `ponytail:` comment): the **live input test** stays on the touch screen — the console's aggregated `MenuSample` is nowhere near a per-device axis/trigger readout; porting it means widening the pad-sample bridge.
## feat(console-ui): the console takes the whole phone panel (`d59a1a96`)
- System bars hidden while the console fronts the app (transient by swipe, restored on dispose) — also the safe-area fix: hidden bars report zero insets, so scrolled content no longer slices off at the visible gesture-bar line.
- Phone density floor of the design-unit scale raised 0.6 → 0.75 (phones-only: the couch term wins on tablets/TVs); still the documented on-glass knob.
- The library's sort/view bar appears only while it holds the pad (▲ / "Sort & view" in the legend), Apple-client style; the field takes the band back as it fades.
- The store/platform subtitle under the focused title is gone (the cover badge already says it); detail band 84 → 64 units.
- Grid guard: the two-column minimum shrinks covers to fit rather than clipping when a narrow viewport × high density leaves less width than two full cells.
## Verification
- `cargo ndk clippy -p punktfunk-client-android -- -D warnings` (arm64): clean at each commit.
- `./gradlew :app:testDebugUnitTest :kit:testDebugUnitTest -PskipRustBuild`: green at each commit (incl. Roborazzi scenes; the console-controllers scene removed with its screen).
- Honest caveat: `pf-console-ui` is cfg-gated to linux/windows/android, so its new unit tests (controllers screen, library) are compile-checked on macOS via the NDK clippy and first *execute* in Linux CI — watch the `ci` workflow on this PR.
- On-glass: debug build installed to a Nothing Phone for the phone-UI pass.
The MainActivity pad probes were one last-writer-wins slot. The Skia shell
installs its probes once (its effect keys never change); a Compose screen the
console opens over itself (Controllers, Licenses) overwrote that slot, and on
its way out nulled it — the shell never re-installed, so every gamepad press
after closing the page was silently dropped until the process died.
The slot is now a stack: each holder pushes its claim on install and removes
it BY IDENTITY on dispose, and dispatch consults the top. Whatever ordering
Compose produces — cross-fades composing both screens at once, non-LIFO
disposal — a leaving screen takes only its own entry, and the one underneath
resurfaces the moment it pops.
Connecting a controller could swap in the console shell over a SurfaceView
nothing would ever paint: the native create failing, the render thread dying,
or a GL context Android reclaimed all left the app on a gray screen for the
rest of the process — Kotlin only logged the Dead event.
SkiaConsole now exposes an observable [healthy] flag (false on create failure
or a Dead event) and App folds it into the gamepad-UI gate, so the touch UI
takes over. On the native side, a run of consecutive GL setup failures
(window surface / Skia wrap) — previously logged and retried forever, a hot
spin with a live surface — now ends the render thread through the same
release order as Quit, which raises Dead and hands the screen back.
The Controllers row used to raise the D7 platform-screen mechanism: Android drew
the Compose ConsoleControllersScreen over the surface and suspended the console's
input until it closed. Now the page is a shared Skia screen
(screens/controllers.rs) pushed like any other settings sub-screen, so the console
keeps its own navigation, transitions and haptics on it — and a future desktop
build gets it for free (the row itself stays Android-only in row_on).
What genuinely cannot move into Rust stays with the host, asked for by ONE
parameterised command, ConsoleCmd::PadAction { action, pad_key }: the USB and
Bluetooth grant dialogs (sc2_bluetooth / sc2_usb / ds_usb), the rumble pulse on
the real InputDevice, and the DualSense pad-audio self test. SkiaConsoleShell
handles them with the same helpers the touch Controllers screen uses (testRumble,
the grant intents, nativePadAudioSelfTest), reporting through the notice toast, so
the support answer cannot drift between interfaces. PadInfo carries the three
fields the screen needed and the aggregated list already lacked (detail line,
forwarded, rumble), filled by ConsoleJson.pads from the same padInfoOf reader the
touch screen renders from.
PlatformScreen::Controllers is gone; the mechanism itself stays for Licenses,
which still suspends the console's input correctly (the probes gate on platformUp
as before). The Compose console variant and its screenshot scenes are deleted;
the touch ControllersScreen keeps the full page INCLUDING the live input test,
which deliberately did not move — the console only receives the aggregated
MenuSample, nowhere near a per-device axis/trigger readout (ponytail note at the
top of controllers.rs records the upgrade path).
Four phone-sized fixes, one theme — a small screen was paying for chrome it
never asked about:
- The Android shell hides the system bars while it fronts the app (transient
by swipe, restored on dispose), the same contract as the stream. This is
also the safe-area fix: hidden bars report zero insets, so scrolled content
no longer slices off at the visible gesture-bar line with bare backdrop
below — only the display cutout remains a real inset.
- The design-unit scale's phone density floor rises 0.6 -> 0.75: on a 460 dpi
panel the floor is what sets the scale (the couch term only wins on tablets
and TVs), and 0.6 read a step too small in the hand. Still the documented
on-glass knob.
- The library's sort/view bar appears only while it holds the pad (up from
the field / the legend's 'Sort & view'), the Apple client's behaviour; the
field takes the band's height back as it fades.
- The store/platform subtitle under the focused title is gone — the cover
badge already says it — and the detail band shrinks 84 -> 64 units, most of
a grid row on a phone.
Plus a guard the new scale makes necessary: the grid's two-column minimum
shrinks its covers to fit rather than clipping at the edges when a narrow
viewport times a high density leaves less width than two full cells.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First adaptation pass of the shared Skia console on Android: the obvious bugs, then the phone-sized UI. Four commits, in dependency order — each builds on the previous, so they ship as one PR.
fix(android): the console's pad probes survive the Controllers/Licenses pages (
a64a22cc)The MainActivity pad probes were one last-writer-wins slot. The Skia shell installs its probes once; a Compose screen the console opened over itself (Controllers, Licenses) overwrote that slot and nulled it on the way out — the shell never re-installed, so every gamepad press after closing the page was silently dropped until the process died. The slot is now a stack: push on install, remove by identity on dispose, dispatch consults the top. Every ordering Compose produces (cross-fades composing both screens at once, non-LIFO disposal) now self-heals.
fix(android): a console that cannot draw yields to the touch UI (
45453103)Connecting a controller could swap in the console over a SurfaceView nothing would ever paint — native create failed, render thread died, or a GL context Android reclaimed — leaving a gray screen for the rest of the process (Kotlin only logged the
Deadevent).SkiaConsole.healthyis now observable state folded into the gamepad-UI gate (touch UI takes over), and the native render loop ends itself through the Quit release order after 3 consecutive GL setup failures instead of hot-spinning gray.feat(console-ui): Connected controllers is the console's own screen (
9cefa0a3)The Kotlin
ConsoleControllersScreentakeover is replaced by a native screen inpf-console-ui— the console keeps its own input on the page (which is what made the probe eviction so common). Only the five platform-only actions go back to the host as oneConsoleCmd::PadAction { action, pad_key }: rumble, SC2 Bluetooth/USB grants, DualSense USB grant, DualSense haptics self-test.PlatformScreen::Controllersis deleted; the D7 suspension mechanism survives for Licenses. The row stays Android-only; desktop ignoresPadAction. Deliberately deferred (recorded as aponytail:comment): the live input test stays on the touch screen — the console's aggregatedMenuSampleis nowhere near a per-device axis/trigger readout; porting it means widening the pad-sample bridge.feat(console-ui): the console takes the whole phone panel (
d59a1a96)Verification
cargo ndk clippy -p punktfunk-client-android -- -D warnings(arm64): clean at each commit../gradlew :app:testDebugUnitTest :kit:testDebugUnitTest -PskipRustBuild: green at each commit (incl. Roborazzi scenes; the console-controllers scene removed with its screen).pf-console-uiis cfg-gated to linux/windows/android, so its new unit tests (controllers screen, library) are compile-checked on macOS via the NDK clippy and first execute in Linux CI — watch theciworkflow on this PR.The Controllers row used to raise the D7 platform-screen mechanism: Android drew the Compose ConsoleControllersScreen over the surface and suspended the console's input until it closed. Now the page is a shared Skia screen (screens/controllers.rs) pushed like any other settings sub-screen, so the console keeps its own navigation, transitions and haptics on it — and a future desktop build gets it for free (the row itself stays Android-only in row_on). What genuinely cannot move into Rust stays with the host, asked for by ONE parameterised command, ConsoleCmd::PadAction { action, pad_key }: the USB and Bluetooth grant dialogs (sc2_bluetooth / sc2_usb / ds_usb), the rumble pulse on the real InputDevice, and the DualSense pad-audio self test. SkiaConsoleShell handles them with the same helpers the touch Controllers screen uses (testRumble, the grant intents, nativePadAudioSelfTest), reporting through the notice toast, so the support answer cannot drift between interfaces. PadInfo carries the three fields the screen needed and the aggregated list already lacked (detail line, forwarded, rumble), filled by ConsoleJson.pads from the same padInfoOf reader the touch screen renders from. PlatformScreen::Controllers is gone; the mechanism itself stays for Licenses, which still suspends the console's input correctly (the probes gate on platformUp as before). The Compose console variant and its screenshot scenes are deleted; the touch ControllersScreen keeps the full page INCLUDING the live input test, which deliberately did not move — the console only receives the aggregated MenuSample, nowhere near a per-device axis/trigger readout (ponytail note at the top of controllers.rs records the upgrade path).enricobuehler referenced this pull request2026-08-18 22:18:46 +00:00