fix(android): P3 keyboard-hint sweep + clipboard JNI verified
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 <noreply@anthropic.com>
This commit is contained in:
@@ -70,12 +70,8 @@ rewrites required.
|
|||||||
2026-05-10.* `spawn_action_button` now nulls the `hotkey`
|
2026-05-10.* `spawn_action_button` now nulls the `hotkey`
|
||||||
argument on Android via a `#[cfg(target_os = "android")]` rebind,
|
argument on Android via a `#[cfg(target_os = "android")]` rebind,
|
||||||
so the U / Esc / F1 / N chips next to the action row labels
|
so the U / Esc / F1 / N chips next to the action row labels
|
||||||
disappear on touch builds. Other hint sites (onboarding panel,
|
disappear on touch builds. Remaining hint sites swept in P3 —
|
||||||
pause-modal `Esc` hint, mode-card hotkey chips on the home
|
see full-keyboard-hint-sweep entry below.
|
||||||
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.
|
|
||||||
- [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action
|
- [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action
|
||||||
button Node carries `min_width: Val::Px(48.0), min_height:
|
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
|
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
|
`[package.metadata.android]` so `aapt` packages the mipmap tree into the
|
||||||
APK, and `icon = "@mipmap/ic_launcher"` to
|
APK, and `icon = "@mipmap/ic_launcher"` to
|
||||||
`[package.metadata.android.application]` so the launcher references it.
|
`[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
|
## P4 — Stability / runtime
|
||||||
|
|
||||||
@@ -186,9 +195,9 @@ rewrites required.
|
|||||||
`card_plugin.rs` is explicit child-only teardown (parent kept alive)
|
`card_plugin.rs` is explicit child-only teardown (parent kept alive)
|
||||||
and is correct. No gameplay bugs attributed to these warnings over 2+
|
and is correct. No gameplay bugs attributed to these warnings over 2+
|
||||||
min AVD runtime.
|
min AVD runtime.
|
||||||
- [x] **AVD functional tests for JNI bridges.** *Partially closed
|
- [x] **AVD functional tests for JNI bridges.** *Closed 2026-05-11.*
|
||||||
2026-05-11.* Pixel 7 AVD (Android 14, x86_64) confirmed running;
|
Pixel 7 AVD (Android 14, x86_64) confirmed running; APK installs
|
||||||
APK installs and runs stable. Key findings:
|
and runs stable. Key findings:
|
||||||
|
|
||||||
**Keystore JNI — verified working.** Forced `SolitaireServerClient`
|
**Keystore JNI — verified working.** Forced `SolitaireServerClient`
|
||||||
by writing a `solitaire_server` settings file, triggering
|
by writing a `solitaire_server` settings file, triggering
|
||||||
@@ -198,12 +207,14 @@ rewrites required.
|
|||||||
completed, correctly returned `NotFound`, and the sync system
|
completed, correctly returned `NotFound`, and the sync system
|
||||||
handled the error gracefully. No panic, no crash from the JNI layer.
|
handled the error gracefully. No panic, no crash from the JNI layer.
|
||||||
|
|
||||||
**Clipboard JNI — not yet exercised at runtime.** The
|
**Clipboard JNI — verified working.** Added a temporary
|
||||||
`android_clipboard::set_text()` path is gated behind
|
`KEYCODE_C` test hook (`avd_clipboard_test` system) to
|
||||||
`Interaction::Pressed` on the share button AND requires a non-null
|
`stats_plugin.rs`, rebuilt the APK, pressed C on the AVD.
|
||||||
`share_url` (only present after a won game is uploaded to a sync
|
Logcat confirmed: `[avd_clipboard_test] clipboard JNI OK` —
|
||||||
server). Both conditions are unreachable in a headless AVD session.
|
`ClipboardManager.setPrimaryClip()` succeeded on Android 14.
|
||||||
The code compiled and linked correctly on `x86_64-linux-android`.
|
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`
|
**Side-finding fixed:** `reqwest`/`hyper-util`'s `GaiResolver`
|
||||||
calls `tokio::runtime::Handle::current()` which panics with "no
|
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 input limitation:** `adb shell input tap` does not deliver
|
||||||
touch events to Bevy/winit on Android 14 + android-activity 0.6.1
|
touch events to Bevy/winit on Android 14 + android-activity 0.6.1
|
||||||
in headless AVD mode. Keyboard events (`KEYCODE_*`) work normally.
|
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -250,9 +250,8 @@ fn spawn_help_screen(commands: &mut Commands, font_res: Option<&FontResource>) {
|
|||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|line| {
|
.with_children(|line| {
|
||||||
// The hotkey rendered as a small chip with a border —
|
// Keyboard chip — suppressed on Android (no keyboard).
|
||||||
// visual cue that it's a key reference, not part of
|
#[cfg(not(target_os = "android"))]
|
||||||
// the description text.
|
|
||||||
line.spawn((
|
line.spawn((
|
||||||
Node {
|
Node {
|
||||||
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
|
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
|
||||||
|
|||||||
@@ -1385,8 +1385,8 @@ fn spawn_mode_card(
|
|||||||
));
|
));
|
||||||
|
|
||||||
if unlocked {
|
if unlocked {
|
||||||
// Hotkey chip — same look as the kbd-chip rows used
|
// Hotkey chip — suppressed on Android (touch builds have no keyboard).
|
||||||
// elsewhere so accelerators read consistently.
|
#[cfg(not(target_os = "android"))]
|
||||||
row.spawn((
|
row.spawn((
|
||||||
Node {
|
Node {
|
||||||
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
|
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
|
||||||
|
|||||||
@@ -944,6 +944,7 @@ fn spawn_overlay(
|
|||||||
},
|
},
|
||||||
TextColor(TEXT_SECONDARY),
|
TextColor(TEXT_SECONDARY),
|
||||||
));
|
));
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
footer.spawn((
|
footer.spawn((
|
||||||
Text::new(keybind_footer_hint_text()),
|
Text::new(keybind_footer_hint_text()),
|
||||||
TextFont {
|
TextFont {
|
||||||
|
|||||||
@@ -328,6 +328,8 @@ pub fn spawn_modal_button<M: Component>(
|
|||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
font_res: Option<&FontResource>,
|
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_handle = font_res.map(|f| f.0.clone()).unwrap_or_default();
|
||||||
let font_label = TextFont {
|
let font_label = TextFont {
|
||||||
font: font_handle.clone(),
|
font: font_handle.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user