keyboard(android): pre-R IME tracking + flush dirty marks during persistent callback

KeyboardPlugin.setupInsetsCallback used to early-return on SDK < R, so
the FFI height stayed at 0 on API 29 devices like the Huawei Mate 20 Pro
— the chat composer never tracked the IME. Run the global-layout
listener on all SDKs, and on pre-R also wire setOnApplyWindowInsetsListener
since EMUI 10's IME-hide dispatches new insets without a follow-up layout
pass. Pre-R IME height comes from systemWindowInsetBottom −
stableInsetBottom (stable insets exclude things that animate in/out).

Inside XKeyboard._onFrame, follow notifyListeners with an explicit
scheduleFrame. _onFrame runs as a persistent frame callback after the
build phase has finished, so setState in listeners marks elements dirty
but ensureVisualUpdate is a no-op in this phase — the steady-state pump
masked the issue while the keyboard was open but on the close-edge
(h transitions to 0) the pump stops and the final rebuild was never
scheduled.
This commit is contained in:
agra
2026-05-22 20:55:07 +03:00
parent 34d3616d16
commit de0a96b557
2 changed files with 42 additions and 9 deletions

View File

@@ -229,9 +229,16 @@ class XKeyboard with ChangeNotifier {
if ((h - _height).abs() > 0.5) {
_height = h;
notifyListeners();
// notifyListeners triggers setState in listeners, which marks elements
// dirty. We're inside a persistent frame callback (after the build phase
// has already run), so `ensureVisualUpdate` is a no-op — without an
// explicit scheduleFrame the dirty marks never get flushed. This matters
// especially on the down-edge (h transitions to 0) where the steady-state
// pump below stops scheduling.
SchedulerBinding.instance.scheduleFrame();
}
// Schedule frames while the curve is still running.
// Steady-state pump while the curve is still running or the keyboard is up.
final curveActive = _isAnimating &&
(ts - _animStartTime) < _animDuration;
if (curveActive || (!Platform.isIOS && (h > 0 || _height > 0))) {