fix(sheets): let system back dismiss a hosted sheet on touch platforms
Back left the Manage Libraries sheet open on Android with no way to dismiss it. The host answered the platform pop with `BackKeyCoordinator.consumeIfHandled()`, which dedups the focused key path against the platform pop. Only TV routes one Back through both; touch platforms never deliver Back to the sheet's key handler, verified on device — a physical Back produced only `popRoute` and no key event. So there was nothing to dedup against, and the global one-shot marker, once set by any other handler, silently swallowed the only signal that closes the sheet. Scopes the dedup to TV. The TV regression that guarded this never set the TV override, so it asserted the swallow on every platform and hid the defect; it now enables the override and a touch counterpart pins the dismissal. Also blocks semantics behind the barrier. The barrier takes every pointer event but left the screen underneath in the semantics tree, so assistive tech and UI automation still saw rows that could not be activated — Maestro read an occluded settings row as visible and tapped its stale coordinates into the sheet. Flutter's own ModalBarrier blocks semantics for the same reason. Verified by replaying the failing Maestro sequence (.maestro/subflows/settings_deep_checks.yaml lines 42-60) on a device: back dismisses the sheet, Services opens, and back returns to settings.
This commit is contained in:
@@ -540,14 +540,22 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
// Barrier + sheet only when open
|
||||
if (_isOpen) ...[
|
||||
Positioned.fill(
|
||||
child: AnimatedBuilder(
|
||||
animation: _barrierAnimation,
|
||||
builder: (context, child) {
|
||||
return GestureDetector(
|
||||
onTap: _barrierDismissible ? () => _close() : null,
|
||||
child: ColoredBox(color: Colors.black.withValues(alpha: _barrierAnimation.value)),
|
||||
);
|
||||
},
|
||||
// The barrier swallows every pointer event behind it, so the
|
||||
// screen underneath must leave the semantics tree too. Without
|
||||
// this, assistive tech (and UI automation) still reads rows that
|
||||
// cannot be activated — including through the ~250ms close, where
|
||||
// a tap on a "visible" row lands on the barrier instead. Flutter's
|
||||
// own ModalBarrier blocks semantics for the same reason.
|
||||
child: BlockSemantics(
|
||||
child: AnimatedBuilder(
|
||||
animation: _barrierAnimation,
|
||||
builder: (context, child) {
|
||||
return GestureDetector(
|
||||
onTap: _barrierDismissible ? () => _close() : null,
|
||||
child: ColoredBox(color: Colors.black.withValues(alpha: _barrierAnimation.value)),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildSheet(context),
|
||||
@@ -569,7 +577,14 @@ class _OverlaySheetHostState extends State<OverlaySheetHost> with SingleTickerPr
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (didPop) return;
|
||||
if (_isOpen && !_isClosing) {
|
||||
if (BackKeyCoordinator.consumeIfHandled()) return;
|
||||
// Only TV routes one Back through both the focused key path and
|
||||
// the platform pop, so only TV needs to dedup them. Touch
|
||||
// platforms never deliver Back to [_handleKeyEvent], so consulting
|
||||
// the global marker here could only ever consume some unrelated
|
||||
// widget's mark and swallow the one signal that closes the sheet.
|
||||
// The marker is global and one-shot, so anything left set by
|
||||
// another handler would strand the sheet open with no way out.
|
||||
if (PlatformDetector.isTV() && BackKeyCoordinator.consumeIfHandled()) return;
|
||||
_handleBack();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user