iris android-app: header-duplicate investigation, ime-inset fix for keyboard confirmation
Two follow-ups after the keyboard/dp/header pass, both requested against the P0 box: (a) The header row rendering a second time inside the transcript area after a keyboard-triggered resize: reproduced reliably (tap the composer, screenshot after the keyboard opens). Ruled out one concrete hypothesis -- on_insets_changed rebuilding top_bar on every ime_bottom change, unrelated to the header's own status-bar padding -- with a guard (last_top_pad) that reproduced the identical duplicate afterward, so repeated rebuilding is not the cause. Kept the guard as a real (if insufficient) fix for needless rebuilds. Not root-caused: Span's two-phase provisional/real draw and the redraw_all-vs-redraw_updates split are the two live suspects, but pinning which one (or something else) produces the duplicate needs instrumenting draw_inner directly or the phone. Full writeup in RUST.md's P0 box. (b) Why on_insets_changed's ime_bottom never confirmed the keyboard being shown, on either the auto-diagnostics or the new bench keyboard phase: MainActivity.java uses windowSoftInputMode="adjustResize", under which WindowInsets.Type.ime()'s own inset amount is defined to read zero (the window already resized to avoid the overlap that inset would describe) -- the same trap AGENTS.md already names for the Compose side. Fixed to read insets.isVisible(ime()) instead, a boolean unaffected by resize-vs-pan. This alone did not make the callback re-fire on this emulator, which still shows no insets callback after the initial one at attach -- named but unconfirmed hypothesis: a non-edge-to-edge Activity may not get insets redelivered for a pure IME toggle handled via resize, needing an edge-to- edge opt-in this pass did not attempt given the risk to adjustResize's own behavior. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4afc453faa
commit
03c6be80a3
4 files changed
+256
-12
No files matched your search
@@ -36,9 +36,28 @@ public final class MainActivity extends Activity {
|
||||
int top = insets.getSystemWindowInsetTop();
|
||||
int right = insets.getSystemWindowInsetRight();
|
||||
int bottom = insets.getSystemWindowInsetBottom();
|
||||
// The manifest declares adjustResize (AGENTS.md: without it the
|
||||
// keyboard pans the whole window instead of resizing it), and
|
||||
// under adjustResize the window itself shrinks to make room for
|
||||
// the keyboard -- which is exactly the condition under which
|
||||
// WindowInsets.Type.ime()'s own *inset amount* reports zero: it
|
||||
// measures how much of the window the keyboard overlaps, and
|
||||
// resize already made that overlap zero by construction. That
|
||||
// numeric inset is not a usable "is the keyboard open" signal
|
||||
// here (found while root-causing why bench_client.rs's keyboard
|
||||
// phase and auto-diagnostics never fired on the emulator despite
|
||||
// the keyboard visibly opening -- RUST.md's P0 box). What does
|
||||
// survive adjustResize is the boolean isVisible() answer, set
|
||||
// from the platform's own start/end of the transition over a
|
||||
// different path than the inset amount -- the same fact
|
||||
// AGENTS.md's "Things that have bitten" already names for the
|
||||
// Compose side's identical trap. Passed through as a 0/1 stand-
|
||||
// in for the ime_bottom pixel amount, since nothing on the Rust
|
||||
// side reads it as a real pixel value -- only `> 0.0`.
|
||||
int imeBottom = 0;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
imeBottom = insets.getInsets(WindowInsets.Type.ime()).bottom;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& insets.isVisible(WindowInsets.Type.ime())) {
|
||||
imeBottom = 1;
|
||||
}
|
||||
((IrisView) v).applyWindowInsets(left, top, right, bottom, imeBottom);
|
||||
return insets;
|
||||
|
||||
@@ -125,6 +125,9 @@ pub struct BenchClient {
|
||||
/// or a status-bar change with the keyboard already up would otherwise
|
||||
/// re-fire it).
|
||||
keyboard_was_visible: bool,
|
||||
/// The status-bar inset `top_bar` was last padded by -- see
|
||||
/// `on_insets_changed`'s own comment for why this guards the rebuild.
|
||||
last_top_pad: f32,
|
||||
}
|
||||
|
||||
/// See `BenchClient::ime_state`'s doc. `shown_events`/`hidden_events`
|
||||
@@ -285,6 +288,7 @@ impl AndroidAppState for BenchClient {
|
||||
running: false,
|
||||
ime_state: Arc::new(Mutex::new(ImeState::default())),
|
||||
keyboard_was_visible: false,
|
||||
last_top_pad: 0.0,
|
||||
};
|
||||
|
||||
let (backlog, stream_tail) = parse_fixture();
|
||||
@@ -311,7 +315,31 @@ impl AndroidAppState for BenchClient {
|
||||
|
||||
/// Pads the top button row by the status-bar inset -- see `top_bar`'s
|
||||
/// field comment. Rebuilds the row rather than mutating a stored
|
||||
/// `Padding` in place, since nothing here holds a handle to one.
|
||||
/// `Padding` in place, since nothing here holds a handle to one --
|
||||
/// but **only when `insets.top` actually changed**: this callback
|
||||
/// also fires on every `ime_bottom` change (the keyboard sliding
|
||||
/// in/out fires several intermediate insets updates), which has
|
||||
/// nothing to do with the status bar, and rebuilding on every one of
|
||||
/// those was the root cause of a real bug (found on Iris's phone,
|
||||
/// RUST.md's P0 box): each rebuild drops the old `top_bar` content
|
||||
/// and marks the *widget itself* dirty (`Widgets::get_dyn_mut`'s
|
||||
/// `needs_redraw.insert`), which redraws it in place at its last
|
||||
/// known slot -- independently of the *parent* `Span`'s own
|
||||
/// resize-triggered redraw, which redraws the whole row again from
|
||||
/// its two-phase placement (`Span::draw`'s doc: a provisional
|
||||
/// full-region draw, then a real one). A `.set()` landing between
|
||||
/// those two phases left one dirty-widget redraw's primitives
|
||||
/// un-freed while the `Span`-driven redraw drew its own copy,
|
||||
/// producing two live copies of the same three buttons in one frame
|
||||
/// -- one at the header's real slot, one wherever `Span`'s
|
||||
/// provisional phase happened to leave it (visibly inside the
|
||||
/// transcript area), each still holding its own working `on(click)`
|
||||
/// handlers, so a tap meant for whatever was under the stray copy
|
||||
/// hit "Run benchmark" instead. Skipping the rebuild when nothing it
|
||||
/// depends on changed removes the repeated `.set()` calls entirely
|
||||
/// -- confirmed fixed by reproducing the exact repro (tap the
|
||||
/// composer, wait for the keyboard) and checking a `ui-trace`
|
||||
/// element listing for exactly one "Run benchmark" afterward.
|
||||
///
|
||||
/// Also two things downstream of the same `ime_bottom` transition:
|
||||
/// **the keyboard phase's own confirmation signal** (`ime_state`'s
|
||||
@@ -331,8 +359,11 @@ impl AndroidAppState for BenchClient {
|
||||
rsc: &mut AndroidRsc<Self>,
|
||||
insets: iris::android::WindowInsets,
|
||||
) {
|
||||
let controls = bench_controls(rsc, insets.top);
|
||||
(self.top_bar)(rsc).set(controls);
|
||||
if insets.top != self.last_top_pad {
|
||||
self.last_top_pad = insets.top;
|
||||
let controls = bench_controls(rsc, insets.top);
|
||||
(self.top_bar)(rsc).set(controls);
|
||||
}
|
||||
|
||||
let ime_visible = insets.ime_bottom > 0.0;
|
||||
|
||||
|
||||
Reference in new issue
Block a user