Merge pull request 'fix(engine): re-poll safe-area insets after app resume' (#121) from fix/safe-area-resume-repoll into master
Build and Deploy / build-and-push (push) Successful in 3m18s
Web WASM Rebuild / rebuild (push) Successful in 8m43s

This commit was merged in pull request #121.
This commit is contained in:
2026-07-06 19:52:59 +00:00
+22 -8
View File
@@ -191,10 +191,12 @@ fn apply_safe_area_to_modal_scrims(
/// whatever `SafeAreaInsets` are current at that moment. /// whatever `SafeAreaInsets` are current at that moment.
/// ///
/// On Android the `android::rearm_on_resumed` system runs in the same frame /// On Android the `android::rearm_on_resumed` system runs in the same frame
/// and resets both `SafeAreaPollTries` and `SafeAreaInsets` to zero, causing /// and resets `SafeAreaPollTries` (the cached `SafeAreaInsets` keep their
/// `refresh_insets` to re-poll JNI over the next few frames. When it resolves /// last-known values), causing `refresh_insets` to re-poll JNI over the next
/// the correct values, `on_safe_area_changed` in `table_plugin` emits a second /// few frames. If the insets changed while backgrounded, `on_safe_area_changed`
/// synthetic `WindowResized` and the layout converges to the right position. /// in `table_plugin` emits a second synthetic `WindowResized` and the layout
/// converges to the right position; if they didn't, nothing is rewritten and
/// the layout stays put.
/// ///
/// On non-Android targets this handler still fires — it ensures that a resume /// On non-Android targets this handler still fires — it ensures that a resume
/// event always refreshes the layout (e.g., after a minimise/restore on /// event always refreshes the layout (e.g., after a minimise/restore on
@@ -232,9 +234,14 @@ mod android {
pub(super) struct SafeAreaPollTries(pub u32); pub(super) struct SafeAreaPollTries(pub u32);
/// Polls Android for safe-area insets until we get a non-zero /// Polls Android for safe-area insets until we get a non-zero
/// reading, then stops. `getRootWindowInsets()` returns `null` (or /// reading, then settles until [`rearm_on_resumed`] re-arms it on the
/// all-zero `Insets`) until the decor view has been laid out, which /// next foreground resume — insets can change while backgrounded
/// is typically frame 13 of a fresh launch. /// (rotation, fold/unfold, gesture ↔ 3-button nav). The poll counter
/// (not `insets.is_populated()`) gates the loop, so a re-armed cycle
/// re-queries JNI even though cached values are already populated.
/// `getRootWindowInsets()` returns `null` (or all-zero `Insets`)
/// until the decor view has been laid out, which is typically frame
/// 13 of a fresh launch.
pub(super) fn refresh_insets( pub(super) fn refresh_insets(
mut insets: ResMut<SafeAreaInsets>, mut insets: ResMut<SafeAreaInsets>,
mut poll: ResMut<SafeAreaPollTries>, mut poll: ResMut<SafeAreaPollTries>,
@@ -243,19 +250,26 @@ mod android {
// devices that genuinely report zero insets. // devices that genuinely report zero insets.
const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps const MAX_TRIES: u32 = 120; // ~2 seconds @ 60 fps
if poll.0 >= MAX_TRIES || insets.is_populated() { if poll.0 >= MAX_TRIES {
return; return;
} }
poll.0 += 1; poll.0 += 1;
match query_insets() { match query_insets() {
Ok(v) if v.is_populated() => { Ok(v) if v.is_populated() => {
if *insets != v {
info!( info!(
"safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)", "safe_area: insets resolved top={} bottom={} left={} right={} (after {} frames)",
v.top, v.bottom, v.left, v.right, poll.0 v.top, v.bottom, v.left, v.right, poll.0
); );
*insets = v; *insets = v;
} }
// Settled for this poll cycle; `rearm_on_resumed` re-arms on
// the next resume. Writing `insets` only on an actual change
// keeps resource change detection (and the relayout it
// triggers) quiet on resumes where nothing moved.
poll.0 = MAX_TRIES;
}
Ok(_) => { Ok(_) => {
// Layout not ready yet; try again next frame. // Layout not ready yet; try again next frame.
} }