From 8a3e30bd1629f6e145ed00d723e36c0a94e76f8c Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 11 May 2026 15:22:40 -0700 Subject: [PATCH] fix(android): P3 keyboard-hint sweep + clipboard JNI verified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suppress all remaining keyboard-accelerator chips/labels on Android: - spawn_modal_button (ui_modal.rs): single cfg gate covers every modal across all 13+ callers (onboarding, pause, confirm, game-over, restore, play-by-seed, home, help, profile, stats, leaderboard, settings, achievement) - home_plugin.rs: mode-card hotkey chips (N/C/Z/X/T) gated off - replay_overlay.rs: [SPACE]/[ESC]/[←→] footer hint text gated off; mode-indicator text kept - help_plugin.rs: kbd chip containers gated off; description text kept Clipboard JNI verified on Pixel 7 AVD (Android 14): added temporary KEYCODE_C test hook, logcat confirmed "clipboard JNI OK", hook reverted. Both JNI bridges (keystore + clipboard) are now confirmed working on device. Co-Authored-By: Claude Sonnet 4.6 --- docs/android/PLAYABILITY_TODO.md | 43 ++++++++++++++++---------- solitaire_engine/src/help_plugin.rs | 5 ++- solitaire_engine/src/home_plugin.rs | 4 +-- solitaire_engine/src/replay_overlay.rs | 1 + solitaire_engine/src/ui_modal.rs | 2 ++ 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/docs/android/PLAYABILITY_TODO.md b/docs/android/PLAYABILITY_TODO.md index f42d47c..b8add6a 100644 --- a/docs/android/PLAYABILITY_TODO.md +++ b/docs/android/PLAYABILITY_TODO.md @@ -70,12 +70,8 @@ rewrites required. 2026-05-10.* `spawn_action_button` now nulls the `hotkey` argument on Android via a `#[cfg(target_os = "android")]` rebind, so the U / Esc / F1 / N chips next to the action row labels - disappear on touch builds. Other hint sites (onboarding panel, - pause-modal `Esc` hint, mode-card hotkey chips on the home - screen, replay overlay footer, modal toggle hints in - profile/stats/leaderboard/settings, help screen) survive — they - live behind navigation and a touch user reaches them less often. - Track as a P3 sweep when more screens are audited on hardware. + disappear on touch builds. Remaining hint sites swept in P3 — + see full-keyboard-hint-sweep entry below. - [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action button Node carries `min_width: Val::Px(48.0), min_height: Val::Px(48.0)` — meets Material's 48 dp baseline on touch and is @@ -170,6 +166,19 @@ rewrites required. `[package.metadata.android]` so `aapt` packages the mipmap tree into the APK, and `icon = "@mipmap/ic_launcher"` to `[package.metadata.android.application]` so the launcher references it. +- [x] **Full keyboard-hint sweep.** *Closed 2026-05-11.* Extended the + P1 suppression to cover all remaining hint sites: + - `ui_modal.rs::spawn_modal_button` — single `#[cfg(target_os = "android")] let hotkey = None;` + line covers every modal button across onboarding, pause, confirm-new-game, + game-over, restore-prompt, play-by-seed, home, help, profile, stats, + leaderboard, settings, and achievement modals simultaneously. + - `home_plugin.rs` — mode-card hotkey chips (N/C/Z/X/T) gated with + `#[cfg(not(target_os = "android"))]` on the chip container. + - `replay_overlay.rs` — `[SPACE]/[ESC]/[←→]` footer hint text gated + with `#[cfg(not(target_os = "android"))]`; mode-indicator text kept. + - `help_plugin.rs` — keyboard chip containers in the controls reference + table gated with `#[cfg(not(target_os = "android"))]`; description + text kept (still useful on touch). ## P4 — Stability / runtime @@ -186,9 +195,9 @@ rewrites required. `card_plugin.rs` is explicit child-only teardown (parent kept alive) and is correct. No gameplay bugs attributed to these warnings over 2+ min AVD runtime. -- [x] **AVD functional tests for JNI bridges.** *Partially closed - 2026-05-11.* Pixel 7 AVD (Android 14, x86_64) confirmed running; - APK installs and runs stable. Key findings: +- [x] **AVD functional tests for JNI bridges.** *Closed 2026-05-11.* + Pixel 7 AVD (Android 14, x86_64) confirmed running; APK installs + and runs stable. Key findings: **Keystore JNI — verified working.** Forced `SolitaireServerClient` by writing a `solitaire_server` settings file, triggering @@ -198,12 +207,14 @@ rewrites required. completed, correctly returned `NotFound`, and the sync system handled the error gracefully. No panic, no crash from the JNI layer. - **Clipboard JNI — not yet exercised at runtime.** The - `android_clipboard::set_text()` path is gated behind - `Interaction::Pressed` on the share button AND requires a non-null - `share_url` (only present after a won game is uploaded to a sync - server). Both conditions are unreachable in a headless AVD session. - The code compiled and linked correctly on `x86_64-linux-android`. + **Clipboard JNI — verified working.** Added a temporary + `KEYCODE_C` test hook (`avd_clipboard_test` system) to + `stats_plugin.rs`, rebuilt the APK, pressed C on the AVD. + Logcat confirmed: `[avd_clipboard_test] clipboard JNI OK` — + `ClipboardManager.setPrimaryClip()` succeeded on Android 14. + Test hook reverted; production clipboard path still requires + `Interaction::Pressed` on the share button with a non-null + `share_url` (won game + sync server). **Side-finding fixed:** `reqwest`/`hyper-util`'s `GaiResolver` calls `tokio::runtime::Handle::current()` which panics with "no @@ -216,8 +227,6 @@ rewrites required. **Touch input limitation:** `adb shell input tap` does not deliver touch events to Bevy/winit on Android 14 + android-activity 0.6.1 in headless AVD mode. Keyboard events (`KEYCODE_*`) work normally. - Full clipboard test requires a real device or a GUI-accessible AVD - session with a won game and active sync server. --- diff --git a/solitaire_engine/src/help_plugin.rs b/solitaire_engine/src/help_plugin.rs index 11e8c45..9f491f6 100644 --- a/solitaire_engine/src/help_plugin.rs +++ b/solitaire_engine/src/help_plugin.rs @@ -250,9 +250,8 @@ fn spawn_help_screen(commands: &mut Commands, font_res: Option<&FontResource>) { ..default() }) .with_children(|line| { - // The hotkey rendered as a small chip with a border — - // visual cue that it's a key reference, not part of - // the description text. + // Keyboard chip — suppressed on Android (no keyboard). + #[cfg(not(target_os = "android"))] line.spawn(( Node { padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1), diff --git a/solitaire_engine/src/home_plugin.rs b/solitaire_engine/src/home_plugin.rs index 8795d93..5898467 100644 --- a/solitaire_engine/src/home_plugin.rs +++ b/solitaire_engine/src/home_plugin.rs @@ -1385,8 +1385,8 @@ fn spawn_mode_card( )); if unlocked { - // Hotkey chip — same look as the kbd-chip rows used - // elsewhere so accelerators read consistently. + // Hotkey chip — suppressed on Android (touch builds have no keyboard). + #[cfg(not(target_os = "android"))] row.spawn(( Node { padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1), diff --git a/solitaire_engine/src/replay_overlay.rs b/solitaire_engine/src/replay_overlay.rs index 9e095dd..debc2df 100644 --- a/solitaire_engine/src/replay_overlay.rs +++ b/solitaire_engine/src/replay_overlay.rs @@ -944,6 +944,7 @@ fn spawn_overlay( }, TextColor(TEXT_SECONDARY), )); + #[cfg(not(target_os = "android"))] footer.spawn(( Text::new(keybind_footer_hint_text()), TextFont { diff --git a/solitaire_engine/src/ui_modal.rs b/solitaire_engine/src/ui_modal.rs index cd2134b..92005ef 100644 --- a/solitaire_engine/src/ui_modal.rs +++ b/solitaire_engine/src/ui_modal.rs @@ -328,6 +328,8 @@ pub fn spawn_modal_button( variant: ButtonVariant, font_res: Option<&FontResource>, ) { + #[cfg(target_os = "android")] + let hotkey = None; let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default(); let font_label = TextFont { font: font_handle.clone(),